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 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
+