pg_typecast 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
+ }
@@ -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
+