pg_typecast 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +20 -0
- data/README.rdoc +68 -0
- data/Rakefile +20 -0
- data/VERSION +1 -0
- data/ext/extconf.rb +11 -0
- data/ext/pg_typecast.c +146 -0
- data/pg_typecast.gemspec +48 -0
- metadata +72 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Stateless Systems
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
= pg_typecast
|
2
|
+
|
3
|
+
* http://github.com/deepfryed/pg_typecast
|
4
|
+
|
5
|
+
== Description
|
6
|
+
|
7
|
+
Provides typecasting support for the pg gem. The code is extracted from the swift gem as
|
8
|
+
a drop-in enhancement for users of the pg gem.
|
9
|
+
|
10
|
+
== Dependencies
|
11
|
+
|
12
|
+
* ruby >= 1.9.1
|
13
|
+
* pg >= 0.9.0
|
14
|
+
|
15
|
+
== Alternatives
|
16
|
+
|
17
|
+
Swift (http://github.com/shanna/swift) is as fast as the pg gem with typecasting support
|
18
|
+
and drivers for mysql, postgresql and db2.
|
19
|
+
|
20
|
+
== Caveats
|
21
|
+
|
22
|
+
* This gem overrides the PGresult#each method.
|
23
|
+
* The rows are returned as hashes with field names as symbols and values typecast from
|
24
|
+
postgres types to ruby types.
|
25
|
+
* Timestamp conversion is done to localtime. The server and client are assumed to be in
|
26
|
+
same timezone unless you use the 'timestamp with time zone' data type in postgresql.
|
27
|
+
|
28
|
+
== Compiling
|
29
|
+
|
30
|
+
If pg_config is not on your PATH, then just set POSTGRES_INCLUDE and POSTGRES_LIB environment variables
|
31
|
+
to the include and lib directories.
|
32
|
+
|
33
|
+
== Synopsis
|
34
|
+
|
35
|
+
require 'pg'
|
36
|
+
require 'pg_typecast'
|
37
|
+
|
38
|
+
adapter = PGconn.connect 'host=127.0.0.1 dbname=test'
|
39
|
+
result = adapter.exec('select * from users')
|
40
|
+
result.each do |row|
|
41
|
+
p row
|
42
|
+
end
|
43
|
+
|
44
|
+
== Typecasting
|
45
|
+
|
46
|
+
The following table illustrates the typecasting done from postgresql types to native ruby types.
|
47
|
+
|
48
|
+
+--------------------+---------------------------+
|
49
|
+
| postgresql type | ruby type |
|
50
|
+
+--------------------+---------------------------+
|
51
|
+
| bool | TrueClass or FalseClass |
|
52
|
+
| bytea | StringIO |
|
53
|
+
| date | Date |
|
54
|
+
| float | Float |
|
55
|
+
| integer | Fixnum or Bignum |
|
56
|
+
| numeric | BigDecimal |
|
57
|
+
| timestamp | Time |
|
58
|
+
| timestampz | Time |
|
59
|
+
+--------------------+---------------------------+
|
60
|
+
|
61
|
+
|
62
|
+
== TODO
|
63
|
+
|
64
|
+
* tests.
|
65
|
+
|
66
|
+
== License
|
67
|
+
|
68
|
+
See LICENSE.
|
data/Rakefile
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'rake'
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'jeweler'
|
5
|
+
Jeweler::Tasks.new do |gem|
|
6
|
+
gem.name = 'pg_typecast'
|
7
|
+
gem.summary = %q{Extensions to pg gem supporting typecasting.}
|
8
|
+
gem.description = %q{Extensions to pg gem supporting typecasting.}
|
9
|
+
gem.email = %w{deepfryed@gmail.com}
|
10
|
+
gem.homepage = 'http://github.com/deepfryed/pg_typecast'
|
11
|
+
gem.authors = ['Bharanee Rathna']
|
12
|
+
gem.extensions = FileList['ext/extconf.rb']
|
13
|
+
gem.files.reject!{|f| f =~ %r{\.gitignore}}
|
14
|
+
|
15
|
+
gem.add_dependency 'pg', '>= 0.9.0'
|
16
|
+
end
|
17
|
+
Jeweler::GemcutterTasks.new
|
18
|
+
rescue LoadError
|
19
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
20
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/ext/extconf.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
require 'mkmf'
|
4
|
+
|
5
|
+
incdir = `pg_config --includedir`.chomp rescue ENV.fetch('POSTGRES_INCLUDE', '/usr/include/postgresql')
|
6
|
+
libdir = `pg_config --libdir`.chomp rescue ENV.fetch('POSTGRES_LIB', '/usr/lib')
|
7
|
+
|
8
|
+
$CFLAGS = "-I#{incdir} -Os"
|
9
|
+
$LDFLAGS = "-L#{libdir} -lpq"
|
10
|
+
|
11
|
+
create_makefile 'pg_typecast'
|
data/ext/pg_typecast.c
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
#include <ruby/ruby.h>
|
2
|
+
#include <ruby/io.h>
|
3
|
+
#include <stdint.h>
|
4
|
+
#include <time.h>
|
5
|
+
#include <libpq-fe.h>
|
6
|
+
#include <libpq/libpq-fs.h>
|
7
|
+
|
8
|
+
#define CONST_GET(scope, constant) rb_funcall(scope, rb_intern("const_get"), 1, rb_str_new2(constant))
|
9
|
+
|
10
|
+
/*
|
11
|
+
Extracted from swift gem.
|
12
|
+
|
13
|
+
1. This speeds up pg when you need typecasting and the ability to whip through results quickly.
|
14
|
+
2. Adds PGresult#each and mixes in enumerable.
|
15
|
+
|
16
|
+
*/
|
17
|
+
|
18
|
+
ID fnew;
|
19
|
+
VALUE cStringIO, cBigDecimal;
|
20
|
+
|
21
|
+
int64_t client_tzoffset(int64_t local, int isdst) {
|
22
|
+
struct tm tm;
|
23
|
+
gmtime_r((const time_t*)&local, &tm);
|
24
|
+
return (int64_t)(local + (isdst ? 3600 : 0) - mktime(&tm));
|
25
|
+
}
|
26
|
+
|
27
|
+
VALUE typecast_timestamp(const char *data, uint64_t len) {
|
28
|
+
struct tm tm;
|
29
|
+
int64_t epoch, adjust, offset;
|
30
|
+
|
31
|
+
char tzsign = 0;
|
32
|
+
int tzhour = 0, tzmin = 0;
|
33
|
+
long double sec_fraction = 0;
|
34
|
+
|
35
|
+
memset(&tm, 0, sizeof(struct tm));
|
36
|
+
if (strchr(data, '.')) {
|
37
|
+
sscanf(data, "%04d-%02d-%02d %02d:%02d:%02d%Lf%c%02d:%02d",
|
38
|
+
&tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec, &sec_fraction,
|
39
|
+
&tzsign, &tzhour, &tzmin);
|
40
|
+
}
|
41
|
+
else {
|
42
|
+
sscanf(data, "%04d-%02d-%02d %02d:%02d:%02d%c%02d:%02d",
|
43
|
+
&tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec,
|
44
|
+
&tzsign, &tzhour, &tzmin);
|
45
|
+
}
|
46
|
+
|
47
|
+
tm.tm_year -= 1900;
|
48
|
+
tm.tm_mon -= 1;
|
49
|
+
tm.tm_isdst = -1;
|
50
|
+
if (tm.tm_mday > 0) {
|
51
|
+
epoch = mktime(&tm);
|
52
|
+
adjust = client_tzoffset(epoch, tm.tm_isdst);
|
53
|
+
offset = adjust;
|
54
|
+
|
55
|
+
if (tzsign) {
|
56
|
+
offset = tzsign == '+'
|
57
|
+
? (time_t)tzhour * 3600 + (time_t)tzmin * 60
|
58
|
+
: (time_t)tzhour * -3600 + (time_t)tzmin * -60;
|
59
|
+
}
|
60
|
+
|
61
|
+
return rb_time_new(epoch+adjust-offset, (uint64_t)(sec_fraction*1000000L));
|
62
|
+
}
|
63
|
+
|
64
|
+
return rb_str_new(data, len);
|
65
|
+
}
|
66
|
+
|
67
|
+
VALUE typecast_date(const char *data, uint64_t len) {
|
68
|
+
return rb_funcall(typecast_timestamp(data, len), rb_intern("to_date"), 0);
|
69
|
+
}
|
70
|
+
|
71
|
+
inline VALUE typecast(const char* data, uint64_t len, int pgtype) {
|
72
|
+
size_t bytea_len;
|
73
|
+
unsigned char* bytea;
|
74
|
+
VALUE rv;
|
75
|
+
|
76
|
+
switch(pgtype) {
|
77
|
+
case 16:
|
78
|
+
return *data == 't' ? Qtrue : Qfalse;
|
79
|
+
case 17:
|
80
|
+
bytea = PQunescapeBytea(data, &bytea_len);
|
81
|
+
rv = rb_funcall(cStringIO, fnew, 1, rb_str_new(bytea, bytea_len));
|
82
|
+
PQfreemem(bytea);
|
83
|
+
return rv;
|
84
|
+
case 20:
|
85
|
+
case 21:
|
86
|
+
case 22:
|
87
|
+
case 23:
|
88
|
+
case 26:
|
89
|
+
return rb_cstr2inum(data, 10);
|
90
|
+
case 700:
|
91
|
+
case 701:
|
92
|
+
case 790:
|
93
|
+
return rb_float_new(atof(data));
|
94
|
+
case 1700:
|
95
|
+
return rb_funcall(cBigDecimal, fnew, 1, rb_str_new(data, len));
|
96
|
+
case 1082:
|
97
|
+
return typecast_date(data, len);
|
98
|
+
case 1114:
|
99
|
+
case 1184:
|
100
|
+
return typecast_timestamp(data, len);
|
101
|
+
default:
|
102
|
+
return rb_str_new(data, len);
|
103
|
+
}
|
104
|
+
}
|
105
|
+
|
106
|
+
VALUE result_each(VALUE self) {
|
107
|
+
int r, c, rows, cols, *types;
|
108
|
+
PGresult *res;
|
109
|
+
Data_Get_Struct(self, PGresult, res);
|
110
|
+
|
111
|
+
VALUE fields = rb_ary_new();
|
112
|
+
rows = PQntuples(res);
|
113
|
+
cols = PQnfields(res);
|
114
|
+
types = (int*)malloc(sizeof(int)*cols);
|
115
|
+
for (c = 0; c < cols; c++) {
|
116
|
+
rb_ary_push(fields, ID2SYM(rb_intern(PQfname(res, c))));
|
117
|
+
types[c] = PQftype(res, c);
|
118
|
+
}
|
119
|
+
|
120
|
+
for (r = 0; r < rows; r++) {
|
121
|
+
VALUE tuple = rb_hash_new();
|
122
|
+
for (c = 0; c < cols; c++) {
|
123
|
+
rb_hash_aset(tuple, rb_ary_entry(fields, c),
|
124
|
+
PQgetisnull(res, r, c) ? Qnil : typecast(PQgetvalue(res, r, c), PQgetlength(res, r, c), types[c]));
|
125
|
+
}
|
126
|
+
rb_yield(tuple);
|
127
|
+
}
|
128
|
+
|
129
|
+
free(types);
|
130
|
+
return Qnil;
|
131
|
+
}
|
132
|
+
|
133
|
+
void Init_pg_typecast() {
|
134
|
+
rb_require("pg");
|
135
|
+
rb_require("date");
|
136
|
+
rb_require("stringio");
|
137
|
+
rb_require("bigdecimal");
|
138
|
+
|
139
|
+
fnew = rb_intern("new");
|
140
|
+
cStringIO = CONST_GET(rb_mKernel, "StringIO");
|
141
|
+
cBigDecimal = CONST_GET(rb_mKernel, "BigDecimal");
|
142
|
+
|
143
|
+
VALUE cPGresult = rb_define_class("PGresult", rb_cObject);
|
144
|
+
rb_include_module(cPGresult, CONST_GET(rb_mKernel, "Enumerable"));
|
145
|
+
rb_define_method(cPGresult, "each", RUBY_METHOD_FUNC(result_each), 0);
|
146
|
+
}
|
data/pg_typecast.gemspec
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{pg_typecast}
|
8
|
+
s.version = "0.1.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Bharanee Rathna"]
|
12
|
+
s.date = %q{2010-09-16}
|
13
|
+
s.description = %q{Extensions to pg gem supporting typecasting.}
|
14
|
+
s.email = ["deepfryed@gmail.com"]
|
15
|
+
s.extensions = ["ext/extconf.rb"]
|
16
|
+
s.extra_rdoc_files = [
|
17
|
+
"LICENSE",
|
18
|
+
"README.rdoc"
|
19
|
+
]
|
20
|
+
s.files = [
|
21
|
+
"LICENSE",
|
22
|
+
"README.rdoc",
|
23
|
+
"Rakefile",
|
24
|
+
"VERSION",
|
25
|
+
"ext/extconf.rb",
|
26
|
+
"ext/pg_typecast.c",
|
27
|
+
"pg_typecast.gemspec"
|
28
|
+
]
|
29
|
+
s.homepage = %q{http://github.com/deepfryed/pg_typecast}
|
30
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
31
|
+
s.require_paths = ["lib"]
|
32
|
+
s.rubygems_version = %q{1.3.5}
|
33
|
+
s.summary = %q{Extensions to pg gem supporting typecasting.}
|
34
|
+
|
35
|
+
if s.respond_to? :specification_version then
|
36
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
37
|
+
s.specification_version = 3
|
38
|
+
|
39
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
40
|
+
s.add_runtime_dependency(%q<pg>, [">= 0.9.0"])
|
41
|
+
else
|
42
|
+
s.add_dependency(%q<pg>, [">= 0.9.0"])
|
43
|
+
end
|
44
|
+
else
|
45
|
+
s.add_dependency(%q<pg>, [">= 0.9.0"])
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
metadata
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pg_typecast
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Bharanee Rathna
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-09-16 00:00:00 +10:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: pg
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.9.0
|
24
|
+
version:
|
25
|
+
description: Extensions to pg gem supporting typecasting.
|
26
|
+
email:
|
27
|
+
- deepfryed@gmail.com
|
28
|
+
executables: []
|
29
|
+
|
30
|
+
extensions:
|
31
|
+
- ext/extconf.rb
|
32
|
+
extra_rdoc_files:
|
33
|
+
- LICENSE
|
34
|
+
- README.rdoc
|
35
|
+
files:
|
36
|
+
- LICENSE
|
37
|
+
- README.rdoc
|
38
|
+
- Rakefile
|
39
|
+
- VERSION
|
40
|
+
- ext/extconf.rb
|
41
|
+
- ext/pg_typecast.c
|
42
|
+
- pg_typecast.gemspec
|
43
|
+
has_rdoc: true
|
44
|
+
homepage: http://github.com/deepfryed/pg_typecast
|
45
|
+
licenses: []
|
46
|
+
|
47
|
+
post_install_message:
|
48
|
+
rdoc_options:
|
49
|
+
- --charset=UTF-8
|
50
|
+
require_paths:
|
51
|
+
- lib
|
52
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: "0"
|
57
|
+
version:
|
58
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: "0"
|
63
|
+
version:
|
64
|
+
requirements: []
|
65
|
+
|
66
|
+
rubyforge_project:
|
67
|
+
rubygems_version: 1.3.5
|
68
|
+
signing_key:
|
69
|
+
specification_version: 3
|
70
|
+
summary: Extensions to pg gem supporting typecasting.
|
71
|
+
test_files: []
|
72
|
+
|