mao 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/lib/mao.rb ADDED
@@ -0,0 +1,180 @@
1
+ require 'pg'
2
+ require 'bigdecimal'
3
+
4
+ # The top-level module to access Mao.
5
+ module Mao
6
+ require 'mao/query'
7
+
8
+ # Connect to the database. +options+ is currently the straight Postgres gem
9
+ # options.
10
+ def self.connect!(options)
11
+ unless @conn
12
+ @conn = PG.connect(options)
13
+ @conn.internal_encoding = Encoding::UTF_8
14
+ end
15
+ end
16
+
17
+ # Disconnect from the database.
18
+ def self.disconnect!
19
+ @conn.close
20
+ @conn = nil
21
+ end
22
+
23
+ # Execute the raw SQL +sql+ with positional +args+. The returned object
24
+ # varies depending on the database vendor.
25
+ def self.sql(sql, *args, &block)
26
+ STDERR.puts "#{sql}#{args ? " " + args.inspect : ""}"
27
+ @conn.exec(sql, *args, &block)
28
+ end
29
+
30
+ # Quote +name+ as appropriate for a table or column name in an SQL statement.
31
+ def self.quote_ident(name)
32
+ case name
33
+ when Symbol
34
+ @conn.quote_ident(name.to_s)
35
+ else
36
+ @conn.quote_ident(name)
37
+ end
38
+ end
39
+
40
+ # Escape +value+ as appropriate for a literal in an SQL statement.
41
+ def self.escape_literal(value)
42
+ case value
43
+ when String
44
+ @conn.escape_literal(value)
45
+ when NilClass
46
+ "null"
47
+ when TrueClass
48
+ "true"
49
+ when FalseClass
50
+ "false"
51
+ when Numeric
52
+ value.to_s
53
+ when Array
54
+ if value == []
55
+ # NULL IN NULL is NULL in SQL, so this is "safe". Empty lists () are
56
+ # apparently part of the standard, but not widely supported (!).
57
+ "(null)"
58
+ else
59
+ "(#{value.map {|v| escape_literal(v)}.join(", ")})"
60
+ end
61
+ when Mao::Query::Raw
62
+ value.text
63
+ when Time
64
+ escape_literal(value.utc.strftime("%Y-%m-%d %H:%M:%S.%6N"))
65
+ else
66
+ raise ArgumentError, "don't know how to escape #{value.class}"
67
+ end
68
+ end
69
+
70
+ # Returns a new Mao::Query object for +table+.
71
+ def self.query(table)
72
+ @queries ||= {}
73
+ @queries[table] ||= Query.new(table).freeze
74
+ end
75
+
76
+ # When raised in a transaction, causes a rollback without the exception
77
+ # bubbling.
78
+ Rollback = Class.new(Exception)
79
+
80
+ # Executes +block+ in a transaction.
81
+ #
82
+ # If +block+ executes without an exception, the transaction is committed.
83
+ #
84
+ # If a Mao::Rollback is raised, the transaction is rolled back, and
85
+ # #transaction returns false.
86
+ #
87
+ # If any other Exception is raised, the transaction is rolled back, and the
88
+ # exception is re-raised.
89
+ #
90
+ # Otherwise, the transaction is committed, and the result of +block+ is
91
+ # returned.
92
+ def self.transaction(&block)
93
+ return block.call if @in_transaction
94
+ @in_transaction = true
95
+
96
+ sql("BEGIN")
97
+ begin
98
+ r = block.call
99
+ rescue Rollback
100
+ sql("ROLLBACK")
101
+ return false
102
+ rescue Exception
103
+ sql("ROLLBACK")
104
+ raise
105
+ ensure
106
+ @in_transaction = false
107
+ end
108
+ sql("COMMIT")
109
+ r
110
+ end
111
+
112
+ # Normalizes the Hash +result+ (of Strings to Strings), with +col_types+
113
+ # specifying Symbol column names to String PostgreSQL types.
114
+ def self.normalize_result(result, col_types)
115
+ Hash[result.map {|k,v|
116
+ k = k.to_sym
117
+ [k, convert_type(v, col_types[k])]
118
+ }]
119
+ end
120
+
121
+ # Normalizes the Hash +result+ (of Strings to Strings), with the joining
122
+ # tables of +from_query+ and +to_query+. Assumes the naming convention for
123
+ # result keys of Mao::Query#join (see Mao::Query#sql) has been followed.
124
+ def self.normalize_join_result(result, from_query, to_query)
125
+ results = {}
126
+ n = 0
127
+
128
+ from_table = from_query.table
129
+ from_types = from_query.col_types
130
+ from_types.keys.sort.each do |k|
131
+ n += 1
132
+ key = "c#{n}"
133
+ if result.include?(key)
134
+ results[from_table] ||= {}
135
+ results[from_table][k] = convert_type(result[key], from_types[k])
136
+ end
137
+ end
138
+
139
+ to_table = to_query.table
140
+ to_types = to_query.col_types
141
+ to_types.keys.sort.each do |k|
142
+ n += 1
143
+ key = "c#{n}"
144
+ if result.include?(key)
145
+ results[to_table] ||= {}
146
+ results[to_table][k] = convert_type(result[key], to_types[k])
147
+ end
148
+ end
149
+
150
+ results
151
+ end
152
+
153
+ # Converts +value+ to a native Ruby value, based on the PostgreSQL type
154
+ # +type+.
155
+ def self.convert_type(value, type)
156
+ return nil if value.nil?
157
+
158
+ case type
159
+ when "integer", "smallint", "bigint", "serial", "bigserial"
160
+ value.to_i
161
+ when /^character varying/, "text"
162
+ value
163
+ when "timestamp without time zone"
164
+ # We assume it's in UTC. (Dangerous?)
165
+ value =~ /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2}(?:\.\d+)?)$/
166
+ Time.new($1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_f, 0)
167
+ when "boolean"
168
+ value == "t"
169
+ when "bytea"
170
+ PG::Connection.unescape_bytea(value)
171
+ when "numeric"
172
+ BigDecimal.new(value)
173
+ else
174
+ STDERR.puts "#{self.name}: unknown type: #{type}"
175
+ value
176
+ end
177
+ end
178
+ end
179
+
180
+ # vim: set sw=2 cc=80 et:
data/mao.gemspec ADDED
@@ -0,0 +1,22 @@
1
+ require File.expand_path('../lib/mao/version', __FILE__)
2
+
3
+ Gem::Specification.new do |gem|
4
+ gem.authors = ["Timothy Leslie Allen", "Arlen Christian Mart Cuss"]
5
+ gem.email = ["allen.timothy.email@gmail.com", "ar@len.me"]
6
+ gem.description = %q{Mao Ain't an ORM}
7
+ gem.summary = %q{A database access layer. Currently supports PG.}
8
+ gem.homepage = "https://github.com/unnali/mao" # TBD
9
+
10
+ gem.add_dependency('pg')
11
+ gem.add_development_dependency('rake')
12
+ gem.add_development_dependency('rspec')
13
+
14
+ gem.files = `git ls-files`.split($\)
15
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
16
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
17
+ gem.name = "mao"
18
+ gem.require_paths = ["lib"]
19
+ gem.version = Mao::VERSION
20
+ end
21
+
22
+ # vim: set sw=2 et cc=80:
@@ -0,0 +1,173 @@
1
+ require 'spec_helper'
2
+
3
+ describe Mao::Filter do
4
+ before { prepare_spec }
5
+
6
+ let(:col_x) { Mao::Filter::Column.new(:name => :x) }
7
+ let(:col_y) { Mao::Filter::Column.new(:name => :y) }
8
+
9
+ describe ".finalize_or_literal" do
10
+ context "with Mao::Filter" do
11
+ before { col_x.should_receive(:finalize).
12
+ with(no_args).and_return("blah") }
13
+ it { Mao::Filter.finalize_or_literal(col_x).should eq "blah" }
14
+ end
15
+
16
+ context "with non-Mao::Filter" do
17
+ before { Mao.should_receive(:escape_literal).
18
+ with(42).and_return("ha") }
19
+ it { Mao::Filter.finalize_or_literal(42).should eq "ha" }
20
+ end
21
+ end
22
+
23
+ describe ".sql" do
24
+ context "Arrays" do
25
+ let(:klass) { double("klass") }
26
+ before { Mao::Filter.should_receive(:const_get).
27
+ with(:Hullo).and_return(klass) }
28
+ before { klass.should_receive(:sql).with(:mao).and_return :dengxiaoping }
29
+ it { Mao::Filter.sql([:Hullo, :mao]).should eq :dengxiaoping }
30
+ end
31
+
32
+ context "Strings" do
33
+ it { Mao::Filter.sql("BATTLE ROYALE").should eq "BATTLE ROYALE" }
34
+ end
35
+ end
36
+
37
+ describe "#and" do
38
+ subject { col_x.and(col_y) }
39
+
40
+ it { should be_an_instance_of Mao::Filter::Binary }
41
+ it { subject.options[:op].should eq "AND" }
42
+ it { subject.options[:lhs].should be col_x }
43
+ it { subject.options[:rhs].should be col_y }
44
+ end
45
+
46
+ describe "#or" do
47
+ subject { col_x.or(col_y) }
48
+
49
+ it { should be_an_instance_of Mao::Filter::Binary }
50
+ it { subject.options[:op].should eq "OR" }
51
+ it { subject.options[:lhs].should be col_x }
52
+ it { subject.options[:rhs].should be col_y }
53
+ end
54
+
55
+ describe "#==" do
56
+ subject { col_x == col_y }
57
+
58
+ it { should be_an_instance_of Mao::Filter::Binary }
59
+ it { subject.options[:op].should eq "=" }
60
+ it { subject.options[:lhs].should be col_x }
61
+ it { subject.options[:rhs].should be col_y }
62
+ end
63
+
64
+ describe "#!=" do
65
+ subject { col_x != col_y }
66
+
67
+ it { should be_an_instance_of Mao::Filter::Binary }
68
+ it { subject.options[:op].should eq "<>" }
69
+ it { subject.options[:lhs].should be col_x }
70
+ it { subject.options[:rhs].should be col_y }
71
+ end
72
+
73
+ describe "#>" do
74
+ subject { col_x > col_y }
75
+
76
+ it { should be_an_instance_of Mao::Filter::Binary }
77
+ it { subject.options[:op].should eq ">" }
78
+ it { subject.options[:lhs].should be col_x }
79
+ it { subject.options[:rhs].should be col_y }
80
+ end
81
+
82
+ describe "#>=" do
83
+ subject { col_x >= col_y }
84
+
85
+ it { should be_an_instance_of Mao::Filter::Binary }
86
+ it { subject.options[:op].should eq ">=" }
87
+ it { subject.options[:lhs].should be col_x }
88
+ it { subject.options[:rhs].should be col_y }
89
+ end
90
+
91
+ describe "#<" do
92
+ subject { col_x < col_y }
93
+
94
+ it { should be_an_instance_of Mao::Filter::Binary }
95
+ it { subject.options[:op].should eq "<" }
96
+ it { subject.options[:lhs].should be col_x }
97
+ it { subject.options[:rhs].should be col_y }
98
+ end
99
+
100
+ describe "#<=" do
101
+ subject { col_x <= col_y }
102
+
103
+ it { should be_an_instance_of Mao::Filter::Binary }
104
+ it { subject.options[:op].should eq "<=" }
105
+ it { subject.options[:lhs].should be col_x }
106
+ it { subject.options[:rhs].should be col_y }
107
+ end
108
+
109
+ describe "#null?" do
110
+ subject { col_x.null? }
111
+
112
+ it { should be_an_instance_of Mao::Filter::Binary }
113
+ it { subject.options[:op].should eq "IS" }
114
+ it { subject.options[:lhs].should be col_x }
115
+ it { subject.options[:rhs].should be_nil }
116
+ end
117
+
118
+ describe "#in" do
119
+ subject { col_x.in([1, 2, 3]) }
120
+
121
+ it { should be_an_instance_of Mao::Filter::Binary }
122
+ it { subject.options[:op].should eq "IN" }
123
+ it { subject.options[:lhs].should be col_x }
124
+ it { subject.options[:rhs].should eq [1, 2, 3] }
125
+ end
126
+ end
127
+
128
+ describe Mao::Filter::Column do
129
+ before { prepare_spec }
130
+
131
+ context "without table" do
132
+ subject { Mao::Filter::Column.new(:name => :Margorth) }
133
+ its(:finalize) { should eq [:Column, :Margorth] }
134
+ it { Mao::Filter.sql(subject.finalize).should eq '"Margorth"' }
135
+ end
136
+
137
+ context "with table" do
138
+ subject { Mao::Filter::Column.new(:table => :Lol, :name => :Margorth) }
139
+ its(:finalize) { should eq [:Column, :Lol, :Margorth] }
140
+ it { Mao::Filter.sql(subject.finalize).should eq '"Lol"."Margorth"' }
141
+ end
142
+ end
143
+
144
+ describe Mao::Filter::Binary do
145
+ before { prepare_spec }
146
+ subject { Mao::Filter::Binary.new(:lhs => 42, :op => '=', :rhs => 42) }
147
+
148
+ its(:finalize) { should eq [:Binary, '=', "42", "42"] }
149
+ it { Mao::Filter.sql(subject.finalize).should eq "(42 = 42)" }
150
+ end
151
+
152
+ describe Mao::Filter::Table do
153
+ before { prepare_spec }
154
+
155
+ context "non-explicit" do
156
+ let(:some) { Mao::Filter::Table.new(Mao.query(:some), false) }
157
+ it { some.value.should be_an_instance_of Mao::Filter::Column }
158
+ it { some.value.finalize.should eq [:Column, :value] }
159
+ end
160
+
161
+ context "explicit" do
162
+ let(:some) { Mao::Filter::Table.new(Mao.query(:some), true) }
163
+ it { some.value.should be_an_instance_of Mao::Filter::Column }
164
+ it { some.value.finalize.should eq [:Column, :some, :value] }
165
+ end
166
+
167
+ context "non-extant" do
168
+ let(:some) { Mao::Filter::Table.new(Mao.query(:some), false) }
169
+ it { expect { some.blargh }.to raise_exception(ArgumentError) }
170
+ end
171
+ end
172
+
173
+ # vim: set sw=2 cc=80 et:
data/spec/fixture.sql ADDED
@@ -0,0 +1,38 @@
1
+ DROP TABLE IF EXISTS empty;
2
+ CREATE TABLE empty (
3
+ id INT,
4
+ value VARCHAR(200));
5
+
6
+ DROP TABLE IF EXISTS one;
7
+ CREATE TABLE one (
8
+ id INT,
9
+ value VARCHAR(200));
10
+
11
+ INSERT INTO one (id, value) VALUES (42, '你好, Dave.');
12
+
13
+ DROP TABLE IF EXISTS "some";
14
+ CREATE TABLE "some" (
15
+ id INT,
16
+ value VARCHAR(100));
17
+
18
+ INSERT INTO "some" (id, value) VALUES (1, 'Bah'), (2, 'Hah'), (3, '你好, Dave.');
19
+
20
+ DROP TABLE IF EXISTS typey;
21
+ CREATE TABLE typey (
22
+ korea BOOLEAN DEFAULT TRUE,
23
+ japan DECIMAL(19,3),
24
+ china BYTEA);
25
+
26
+ INSERT INTO typey (korea, japan, china) VALUES (true, 1234567890123456.789, E'WHAT\\000'), (false, -1234567890123456.789, E'HUH\\001\\002');
27
+
28
+ DROP TABLE IF EXISTS autoid;
29
+ CREATE TABLE autoid (
30
+ id SERIAL PRIMARY KEY NOT NULL,
31
+ value VARCHAR(100));
32
+
33
+ DROP TABLE IF EXISTS times;
34
+ CREATE TABLE times (
35
+ id SERIAL PRIMARY KEY NOT NULL,
36
+ time TIMESTAMP WITHOUT TIME ZONE NOT NULL);
37
+
38
+ INSERT INTO times (time) VALUES ('2012-11-10T19:45:00Z');
data/spec/mao_spec.rb ADDED
@@ -0,0 +1,220 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe Mao do
5
+ before { prepare_spec }
6
+
7
+ describe ".connect!" do
8
+ let(:options) { double("options") }
9
+ let(:conn) { double("conn") }
10
+ before { PG.should_receive(:connect).with(options).and_return(conn) }
11
+ before { conn.should_receive(:internal_encoding=).with(Encoding::UTF_8) }
12
+ before { Mao.disconnect! rescue false }
13
+ it { Mao.connect!(options) }
14
+ after { Mao.instance_variable_set("@conn", nil) }
15
+ end
16
+
17
+ describe ".disconnect!" do
18
+ before { PG::Connection.any_instance.should_receive(:close) }
19
+ it { Mao.disconnect! }
20
+ end
21
+
22
+ describe ".sql" do
23
+ before { PG::Connection.any_instance.should_receive(:exec).
24
+ with(:x).and_return(:y) }
25
+ it { Mao.sql(:x).should eq :y }
26
+ end
27
+
28
+ describe ".quote_ident" do
29
+ context "pass-thru" do
30
+ before { PG::Connection.any_instance.should_receive(:quote_ident).
31
+ with("table").and_return(%q{"table"}) }
32
+ it { Mao.quote_ident("table").should eq %q{"table"} }
33
+ end
34
+
35
+ context "Symbols" do
36
+ before { PG::Connection.any_instance.should_receive(:quote_ident).
37
+ with("table").and_return(%q{"table"}) }
38
+ it { Mao.quote_ident(:table).should eq %q{"table"} }
39
+ end
40
+ end
41
+
42
+ describe ".escape_literal" do
43
+ describe "verify pass-thru String" do
44
+ before { PG::Connection.any_instance.should_receive(:escape_literal).
45
+ with("table").and_return(%q{'table'}) }
46
+ it { Mao.escape_literal("table").should eq %q{'table'} }
47
+ end
48
+
49
+ describe "verify not pass-thru others" do
50
+ before { PG::Connection.any_instance.
51
+ should_not_receive(:escape_literal) }
52
+ it { Mao.escape_literal(nil).should eq "null" }
53
+ end
54
+
55
+ describe "actual values" do
56
+ it { Mao.escape_literal("table").should eq %q{'table'} }
57
+ it { Mao.escape_literal(42).should eq %q{42} }
58
+ it { Mao.escape_literal(true).should eq %q{true} }
59
+ it { Mao.escape_literal(false).should eq %q{false} }
60
+ it { Mao.escape_literal(nil).should eq %q{null} }
61
+ it { Mao.escape_literal([]).should eq %q{(null)} }
62
+ it { Mao.escape_literal([1]).should eq %q{(1)} }
63
+ it { Mao.escape_literal([1, "xzy"]).should eq %q{(1, 'xzy')} }
64
+ it { Mao.escape_literal(Mao::Query.raw("\n\"'%")).should eq "\n\"'%" }
65
+
66
+ # Times are escaped to UTC always.
67
+ it { Mao.escape_literal(Time.new(2012, 11, 11, 6, 45, 0, 11 * 3600)).
68
+ should eq %q{'2012-11-10 19:45:00.000000'} }
69
+ it { Mao.escape_literal(Time.new(2012, 11, 10, 19, 45, 0, 0)).
70
+ should eq %q{'2012-11-10 19:45:00.000000'} }
71
+ it { Mao.escape_literal(Time.new(2012, 11, 10, 19, 45, 0.1, 0)).
72
+ should eq %q{'2012-11-10 19:45:00.100000'} }
73
+ end
74
+ end
75
+
76
+ describe ".query" do
77
+ subject { Mao.query(:empty) }
78
+ it { should be_an_instance_of Mao::Query }
79
+ it { should be_frozen }
80
+ end
81
+
82
+ describe ".transaction" do
83
+ context "empty" do
84
+ before { PG::Connection.any_instance.should_receive(:exec).
85
+ with("BEGIN") }
86
+ before { PG::Connection.any_instance.should_receive(:exec).
87
+ with("COMMIT") }
88
+ it { Mao.transaction {} }
89
+ end
90
+
91
+ context "success" do
92
+ before { Mao.should_receive(:sql).with("BEGIN") }
93
+ before { Mao.should_receive(:sql).with(:some_sql).and_return :ok }
94
+ before { Mao.should_receive(:sql).with("COMMIT") }
95
+ it { Mao.transaction { Mao.sql(:some_sql) }.
96
+ should eq :ok }
97
+ end
98
+
99
+ context "failure" do
100
+ before { Mao.should_receive(:sql).with("BEGIN") }
101
+ before { Mao.should_receive(:sql).with(:some_sql).
102
+ and_raise(Exception.new) }
103
+ before { Mao.should_receive(:sql).with("ROLLBACK") }
104
+ it { expect { Mao.transaction { Mao.sql(:some_sql) }
105
+ }.to raise_exception }
106
+ end
107
+
108
+ context "rollback" do
109
+ before { Mao.should_receive(:sql).with("BEGIN") }
110
+ before { Mao.should_receive(:sql).with(:some_sql).
111
+ and_raise(Mao::Rollback) }
112
+ before { Mao.should_receive(:sql).with("ROLLBACK") }
113
+ it { expect { Mao.transaction { Mao.sql(:some_sql) }
114
+ }.to_not raise_exception }
115
+ end
116
+
117
+ context "nested transactions" do
118
+ # Currently not supported: the inner transactions don't add transactions
119
+ # at all.
120
+ before { Mao.should_receive(:sql).with("BEGIN").once }
121
+ before { Mao.should_receive(:sql).with("ROLLBACK").once }
122
+
123
+ it do
124
+ Mao.transaction { Mao.transaction { raise Mao::Rollback } }.
125
+ should be_false
126
+ end
127
+ end
128
+ end
129
+
130
+ describe ".normalize_result" do
131
+ before { Mao.should_receive(:convert_type).
132
+ with("y", "zzz").and_return("q") }
133
+ it { Mao.normalize_result({"x" => "y"}, {:x => "zzz"}).
134
+ should eq({:x => "q"}) }
135
+ end
136
+
137
+ describe ".normalize_join_result" do
138
+ let(:from) { double("from") }
139
+ let(:to) { double("to") }
140
+
141
+ before { from.should_receive(:table).and_return(:from) }
142
+ before { from.should_receive(:col_types).and_return({:a => "integer"}) }
143
+ before { to.should_receive(:table).and_return(:to) }
144
+ before { to.should_receive(:col_types).
145
+ and_return({:b => "character varying"}) }
146
+
147
+ it { Mao.normalize_join_result(
148
+ {"c1" => "1", "c2" => "2"}, from, to).
149
+ should eq({:from => {:a => 1},
150
+ :to => {:b => "2"}}) }
151
+
152
+ it { Mao.normalize_join_result(
153
+ {"c1" => "1"}, from, to).
154
+ should eq({:from => {:a => 1}}) }
155
+ end
156
+
157
+ describe ".convert_type" do
158
+ context "integers" do
159
+ it { Mao.convert_type(nil, "integer").should be_nil }
160
+ it { Mao.convert_type("42", "integer").should eq 42 }
161
+ it { Mao.convert_type("42", "smallint").should eq 42 }
162
+ it { Mao.convert_type("42", "bigint").should eq 42 }
163
+ it { Mao.convert_type("42", "serial").should eq 42 }
164
+ it { Mao.convert_type("42", "bigserial").should eq 42 }
165
+ end
166
+
167
+ context "character" do
168
+ it { Mao.convert_type(nil, "character varying").should be_nil }
169
+ it { Mao.convert_type("blah", "character varying").
170
+ should eq "blah" }
171
+ it { Mao.convert_type("blah", "character varying").encoding.
172
+ should be Encoding::UTF_8 }
173
+
174
+ it { Mao.convert_type(nil, "character varying(200)").should be_nil }
175
+ it { Mao.convert_type("blah", "character varying(200)").
176
+ should eq "blah" }
177
+ it { Mao.convert_type("blah", "character varying(200)").encoding.
178
+ should be Encoding::UTF_8 }
179
+
180
+ it { Mao.convert_type(nil, "text").should be_nil }
181
+ it { Mao.convert_type("blah", "text").
182
+ should eq "blah" }
183
+ it { Mao.convert_type("blah", "text").encoding.
184
+ should be Encoding::UTF_8 }
185
+ end
186
+
187
+ context "dates" do
188
+ it { Mao.convert_type(nil, "timestamp without time zone").
189
+ should be_nil }
190
+ # Note: without timezone is assumed to be in UTC.
191
+ it { Mao.convert_type("2012-11-10 19:45:00",
192
+ "timestamp without time zone").
193
+ should eq Time.new(2012, 11, 10, 19, 45, 0, 0) }
194
+ it { Mao.convert_type("2012-11-10 19:45:00.1",
195
+ "timestamp without time zone").
196
+ should eq Time.new(2012, 11, 10, 19, 45, 0.1, 0) }
197
+ end
198
+
199
+ context "booleans" do
200
+ it { Mao.convert_type(nil, "boolean").should be_nil }
201
+ it { Mao.convert_type("t", "boolean").should eq true }
202
+ it { Mao.convert_type("f", "boolean").should eq false }
203
+ end
204
+
205
+ context "bytea" do
206
+ it { Mao.convert_type(nil, "bytea").should be_nil }
207
+ it { Mao.convert_type("\\x5748415400", "bytea").should eq "WHAT\x00" }
208
+ it { Mao.convert_type("\\x5748415400", "bytea").encoding.
209
+ should eq Encoding::ASCII_8BIT }
210
+ end
211
+
212
+ context "numeric" do
213
+ it { Mao.convert_type(nil, "numeric").should be_nil }
214
+ it { Mao.convert_type("1234567890123456.789", "numeric").
215
+ should eq BigDecimal.new("1234567890123456.789") }
216
+ end
217
+ end
218
+ end
219
+
220
+ # vim: set sw=2 cc=80 et: