mao 0.0.3

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/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: