epugh-sequel 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (134) hide show
  1. data/README.rdoc +652 -0
  2. data/VERSION.yml +4 -0
  3. data/bin/sequel +104 -0
  4. data/lib/sequel.rb +1 -0
  5. data/lib/sequel/adapters/ado.rb +85 -0
  6. data/lib/sequel/adapters/db2.rb +132 -0
  7. data/lib/sequel/adapters/dbi.rb +101 -0
  8. data/lib/sequel/adapters/do.rb +197 -0
  9. data/lib/sequel/adapters/do/mysql.rb +38 -0
  10. data/lib/sequel/adapters/do/postgres.rb +92 -0
  11. data/lib/sequel/adapters/do/sqlite.rb +31 -0
  12. data/lib/sequel/adapters/firebird.rb +307 -0
  13. data/lib/sequel/adapters/informix.rb +75 -0
  14. data/lib/sequel/adapters/jdbc.rb +485 -0
  15. data/lib/sequel/adapters/jdbc/h2.rb +62 -0
  16. data/lib/sequel/adapters/jdbc/mysql.rb +56 -0
  17. data/lib/sequel/adapters/jdbc/oracle.rb +23 -0
  18. data/lib/sequel/adapters/jdbc/postgresql.rb +101 -0
  19. data/lib/sequel/adapters/jdbc/sqlite.rb +43 -0
  20. data/lib/sequel/adapters/mysql.rb +370 -0
  21. data/lib/sequel/adapters/odbc.rb +184 -0
  22. data/lib/sequel/adapters/openbase.rb +57 -0
  23. data/lib/sequel/adapters/oracle.rb +140 -0
  24. data/lib/sequel/adapters/postgres.rb +453 -0
  25. data/lib/sequel/adapters/shared/mssql.rb +93 -0
  26. data/lib/sequel/adapters/shared/mysql.rb +341 -0
  27. data/lib/sequel/adapters/shared/oracle.rb +62 -0
  28. data/lib/sequel/adapters/shared/postgres.rb +743 -0
  29. data/lib/sequel/adapters/shared/progress.rb +34 -0
  30. data/lib/sequel/adapters/shared/sqlite.rb +263 -0
  31. data/lib/sequel/adapters/sqlite.rb +243 -0
  32. data/lib/sequel/adapters/utils/date_format.rb +21 -0
  33. data/lib/sequel/adapters/utils/stored_procedures.rb +75 -0
  34. data/lib/sequel/adapters/utils/unsupported.rb +62 -0
  35. data/lib/sequel/connection_pool.rb +258 -0
  36. data/lib/sequel/core.rb +204 -0
  37. data/lib/sequel/core_sql.rb +185 -0
  38. data/lib/sequel/database.rb +687 -0
  39. data/lib/sequel/database/schema_generator.rb +324 -0
  40. data/lib/sequel/database/schema_methods.rb +164 -0
  41. data/lib/sequel/database/schema_sql.rb +324 -0
  42. data/lib/sequel/dataset.rb +422 -0
  43. data/lib/sequel/dataset/convenience.rb +237 -0
  44. data/lib/sequel/dataset/prepared_statements.rb +220 -0
  45. data/lib/sequel/dataset/sql.rb +1105 -0
  46. data/lib/sequel/deprecated.rb +529 -0
  47. data/lib/sequel/exceptions.rb +44 -0
  48. data/lib/sequel/extensions/blank.rb +42 -0
  49. data/lib/sequel/extensions/inflector.rb +288 -0
  50. data/lib/sequel/extensions/pagination.rb +96 -0
  51. data/lib/sequel/extensions/pretty_table.rb +78 -0
  52. data/lib/sequel/extensions/query.rb +48 -0
  53. data/lib/sequel/extensions/string_date_time.rb +47 -0
  54. data/lib/sequel/metaprogramming.rb +44 -0
  55. data/lib/sequel/migration.rb +212 -0
  56. data/lib/sequel/model.rb +142 -0
  57. data/lib/sequel/model/association_reflection.rb +263 -0
  58. data/lib/sequel/model/associations.rb +1024 -0
  59. data/lib/sequel/model/base.rb +911 -0
  60. data/lib/sequel/model/deprecated.rb +188 -0
  61. data/lib/sequel/model/deprecated_hooks.rb +103 -0
  62. data/lib/sequel/model/deprecated_inflector.rb +335 -0
  63. data/lib/sequel/model/deprecated_validations.rb +384 -0
  64. data/lib/sequel/model/errors.rb +37 -0
  65. data/lib/sequel/model/exceptions.rb +7 -0
  66. data/lib/sequel/model/inflections.rb +230 -0
  67. data/lib/sequel/model/plugins.rb +74 -0
  68. data/lib/sequel/object_graph.rb +230 -0
  69. data/lib/sequel/plugins/caching.rb +122 -0
  70. data/lib/sequel/plugins/hook_class_methods.rb +122 -0
  71. data/lib/sequel/plugins/schema.rb +53 -0
  72. data/lib/sequel/plugins/single_table_inheritance.rb +63 -0
  73. data/lib/sequel/plugins/validation_class_methods.rb +373 -0
  74. data/lib/sequel/sql.rb +854 -0
  75. data/lib/sequel/version.rb +11 -0
  76. data/lib/sequel_core.rb +1 -0
  77. data/lib/sequel_model.rb +1 -0
  78. data/spec/adapters/ado_spec.rb +46 -0
  79. data/spec/adapters/firebird_spec.rb +376 -0
  80. data/spec/adapters/informix_spec.rb +96 -0
  81. data/spec/adapters/mysql_spec.rb +875 -0
  82. data/spec/adapters/oracle_spec.rb +272 -0
  83. data/spec/adapters/postgres_spec.rb +692 -0
  84. data/spec/adapters/spec_helper.rb +10 -0
  85. data/spec/adapters/sqlite_spec.rb +550 -0
  86. data/spec/core/connection_pool_spec.rb +526 -0
  87. data/spec/core/core_ext_spec.rb +156 -0
  88. data/spec/core/core_sql_spec.rb +528 -0
  89. data/spec/core/database_spec.rb +1214 -0
  90. data/spec/core/dataset_spec.rb +3513 -0
  91. data/spec/core/expression_filters_spec.rb +363 -0
  92. data/spec/core/migration_spec.rb +261 -0
  93. data/spec/core/object_graph_spec.rb +280 -0
  94. data/spec/core/pretty_table_spec.rb +58 -0
  95. data/spec/core/schema_generator_spec.rb +167 -0
  96. data/spec/core/schema_spec.rb +778 -0
  97. data/spec/core/spec_helper.rb +82 -0
  98. data/spec/core/version_spec.rb +7 -0
  99. data/spec/extensions/blank_spec.rb +67 -0
  100. data/spec/extensions/caching_spec.rb +201 -0
  101. data/spec/extensions/hook_class_methods_spec.rb +470 -0
  102. data/spec/extensions/inflector_spec.rb +122 -0
  103. data/spec/extensions/pagination_spec.rb +99 -0
  104. data/spec/extensions/pretty_table_spec.rb +91 -0
  105. data/spec/extensions/query_spec.rb +85 -0
  106. data/spec/extensions/schema_spec.rb +111 -0
  107. data/spec/extensions/single_table_inheritance_spec.rb +53 -0
  108. data/spec/extensions/spec_helper.rb +90 -0
  109. data/spec/extensions/string_date_time_spec.rb +93 -0
  110. data/spec/extensions/validation_class_methods_spec.rb +1054 -0
  111. data/spec/integration/dataset_test.rb +160 -0
  112. data/spec/integration/eager_loader_test.rb +683 -0
  113. data/spec/integration/prepared_statement_test.rb +130 -0
  114. data/spec/integration/schema_test.rb +183 -0
  115. data/spec/integration/spec_helper.rb +75 -0
  116. data/spec/integration/type_test.rb +96 -0
  117. data/spec/model/association_reflection_spec.rb +93 -0
  118. data/spec/model/associations_spec.rb +1780 -0
  119. data/spec/model/base_spec.rb +494 -0
  120. data/spec/model/caching_spec.rb +217 -0
  121. data/spec/model/dataset_methods_spec.rb +78 -0
  122. data/spec/model/eager_loading_spec.rb +1165 -0
  123. data/spec/model/hooks_spec.rb +472 -0
  124. data/spec/model/inflector_spec.rb +126 -0
  125. data/spec/model/model_spec.rb +588 -0
  126. data/spec/model/plugins_spec.rb +142 -0
  127. data/spec/model/record_spec.rb +1243 -0
  128. data/spec/model/schema_spec.rb +92 -0
  129. data/spec/model/spec_helper.rb +124 -0
  130. data/spec/model/validations_spec.rb +1080 -0
  131. data/spec/rcov.opts +6 -0
  132. data/spec/spec.opts +0 -0
  133. data/spec/spec_config.rb.example +10 -0
  134. metadata +202 -0
@@ -0,0 +1,184 @@
1
+ require 'odbc'
2
+
3
+ module Sequel
4
+ module ODBC
5
+ class Database < Sequel::Database
6
+ set_adapter_scheme :odbc
7
+
8
+ GUARDED_DRV_NAME = /^\{.+\}$/.freeze
9
+ DRV_NAME_GUARDS = '{%s}'.freeze
10
+
11
+ def initialize(opts)
12
+ super(opts)
13
+ case opts[:db_type]
14
+ when 'mssql'
15
+ Sequel.require 'adapters/shared/mssql'
16
+ extend Sequel::MSSQL::DatabaseMethods
17
+ when 'progress'
18
+ Sequel.require 'adapters/shared/progress'
19
+ extend Sequel::Progress::DatabaseMethods
20
+ end
21
+ end
22
+
23
+ def connect(server)
24
+ opts = server_opts(server)
25
+ if opts.include? :driver
26
+ drv = ::ODBC::Driver.new
27
+ drv.name = 'Sequel ODBC Driver130'
28
+ opts.each do |param, value|
29
+ if :driver == param and not (value =~ GUARDED_DRV_NAME)
30
+ value = DRV_NAME_GUARDS % value
31
+ end
32
+ drv.attrs[param.to_s.capitalize] = value
33
+ end
34
+ db = ::ODBC::Database.new
35
+ conn = db.drvconnect(drv)
36
+ else
37
+ conn = ::ODBC::connect(opts[:database], opts[:user], opts[:password])
38
+ end
39
+ conn.autocommit = true
40
+ conn
41
+ end
42
+
43
+ def dataset(opts = nil)
44
+ ODBC::Dataset.new(self, opts)
45
+ end
46
+
47
+ # ODBC returns native statement objects, which must be dropped if
48
+ # you call execute manually, or you will get warnings. See the
49
+ # fetch_rows method source code for an example of how to drop
50
+ # the statements.
51
+ def execute(sql, opts={})
52
+ log_info(sql)
53
+ synchronize(opts[:server]) do |conn|
54
+ r = conn.run(sql)
55
+ yield(r) if block_given?
56
+ r
57
+ end
58
+ end
59
+
60
+ def execute_dui(sql, opts={})
61
+ log_info(sql)
62
+ synchronize(opts[:server]){|conn| conn.do(sql)}
63
+ end
64
+ alias_method :do, :execute_dui
65
+
66
+ # Support single level transactions on ODBC
67
+ def transaction(opts={})
68
+ unless opts.is_a?(Hash)
69
+ Deprecation.deprecate('Passing an argument other than a Hash to Database#transaction', "Use DB.transaction(:server=>#{opts.inspect})")
70
+ opts = {:server=>opts}
71
+ end
72
+ synchronize(opts[:server]) do |conn|
73
+ return yield(conn) if @transactions.include?(Thread.current)
74
+ log_info(begin_transaction_sql)
75
+ conn.do(begin_transaction_sql)
76
+ begin
77
+ @transactions << Thread.current
78
+ yield(conn)
79
+ rescue ::Exception => e
80
+ log_info(rollback_transaction_sql)
81
+ conn.do(rollback_transaction_sql)
82
+ transaction_error(e)
83
+ ensure
84
+ unless e
85
+ log_info(commit_transaction_sql)
86
+ conn.do(commit_transaction_sql)
87
+ end
88
+ @transactions.delete(Thread.current)
89
+ end
90
+ end
91
+ end
92
+
93
+ private
94
+
95
+ def disconnect_connection(c)
96
+ c.disconnect
97
+ end
98
+ end
99
+
100
+ class Dataset < Sequel::Dataset
101
+ BOOL_TRUE = '1'.freeze
102
+ BOOL_FALSE = '0'.freeze
103
+ ODBC_TIMESTAMP_FORMAT = "{ts '%Y-%m-%d %H:%M:%S'}".freeze
104
+ ODBC_TIMESTAMP_AFTER_SECONDS =
105
+ ODBC_TIMESTAMP_FORMAT.index( '%S' ).succ - ODBC_TIMESTAMP_FORMAT.length
106
+ ODBC_DATE_FORMAT = "{d '%Y-%m-%d'}".freeze
107
+ UNTITLED_COLUMN = 'untitled_%d'.freeze
108
+
109
+ def fetch_rows(sql, &block)
110
+ execute(sql) do |s|
111
+ begin
112
+ untitled_count = 0
113
+ @columns = s.columns(true).map do |c|
114
+ if (n = c.name).empty?
115
+ n = UNTITLED_COLUMN % (untitled_count += 1)
116
+ end
117
+ output_identifier(n)
118
+ end
119
+ rows = s.fetch_all
120
+ rows.each {|row| yield hash_row(row)} if rows
121
+ ensure
122
+ s.drop unless s.nil? rescue nil
123
+ end
124
+ end
125
+ self
126
+ end
127
+
128
+ private
129
+
130
+ def convert_odbc_value(v)
131
+ # When fetching a result set, the Ruby ODBC driver converts all ODBC
132
+ # SQL types to an equivalent Ruby type; with the exception of
133
+ # SQL_TYPE_DATE, SQL_TYPE_TIME and SQL_TYPE_TIMESTAMP.
134
+ #
135
+ # The conversions below are consistent with the mappings in
136
+ # ODBCColumn#mapSqlTypeToGenericType and Column#klass.
137
+ case v
138
+ when ::ODBC::TimeStamp
139
+ DateTime.new(v.year, v.month, v.day, v.hour, v.minute, v.second)
140
+ when ::ODBC::Time
141
+ now = DateTime.now
142
+ Time.gm(now.year, now.month, now.day, v.hour, v.minute, v.second)
143
+ when ::ODBC::Date
144
+ Date.new(v.year, v.month, v.day)
145
+ else
146
+ v
147
+ end
148
+ end
149
+
150
+ def hash_row(row)
151
+ hash = {}
152
+ row.each_with_index do |v, idx|
153
+ hash[@columns[idx]] = convert_odbc_value(v)
154
+ end
155
+ hash
156
+ end
157
+
158
+ def literal_date(v)
159
+ v.strftime(ODBC_DATE_FORMAT)
160
+ end
161
+
162
+ def literal_datetime(v)
163
+ formatted = v.strftime(ODBC_TIMESTAMP_FORMAT)
164
+ usec = v.sec_fraction * 86400000000
165
+ formatted.insert(ODBC_TIMESTAMP_AFTER_SECONDS, ".#{(usec.to_f/1000).round}") if usec >= 1000
166
+ formatted
167
+ end
168
+
169
+ def literal_false
170
+ BOOL_FALSE
171
+ end
172
+
173
+ def literal_true
174
+ BOOL_TRUE
175
+ end
176
+
177
+ def literal_time(v)
178
+ formatted = v.strftime(ODBC_TIMESTAMP_FORMAT)
179
+ formatted.insert(ODBC_TIMESTAMP_AFTER_SECONDS, ".#{(v.usec.to_f/1000).round}") if usec >= 1000
180
+ formatted
181
+ end
182
+ end
183
+ end
184
+ end
@@ -0,0 +1,57 @@
1
+ require 'openbase'
2
+
3
+ module Sequel
4
+ module OpenBase
5
+ class Database < Sequel::Database
6
+ set_adapter_scheme :openbase
7
+
8
+ def connect(server)
9
+ opts = server_opts(server)
10
+ OpenBase.new(
11
+ opts[:database],
12
+ opts[:host] || 'localhost',
13
+ opts[:user],
14
+ opts[:password]
15
+ )
16
+ end
17
+
18
+ def dataset(opts = nil)
19
+ OpenBase::Dataset.new(self, opts)
20
+ end
21
+
22
+ def execute(sql, opts={})
23
+ log_info(sql)
24
+ synchronize(opts[:server]) do |conn|
25
+ r = conn.execute(sql)
26
+ yield(r) if block_given?
27
+ r
28
+ end
29
+ end
30
+ alias_method :do, :execute
31
+
32
+ private
33
+
34
+ def disconnect_connection(c)
35
+ c.disconnect
36
+ end
37
+ end
38
+
39
+ class Dataset < Sequel::Dataset
40
+ def fetch_rows(sql)
41
+ execute(sql) do |result|
42
+ begin
43
+ @columns = result.column_infos.map{|c| output_identifier(c.name)}
44
+ result.each do |r|
45
+ row = {}
46
+ r.each_with_index {|v, i| row[@columns[i]] = v}
47
+ yield row
48
+ end
49
+ ensure
50
+ # result.close
51
+ end
52
+ end
53
+ self
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,140 @@
1
+ require 'oci8'
2
+ Sequel.require 'adapters/shared/oracle'
3
+
4
+ module Sequel
5
+ module Oracle
6
+ class Database < Sequel::Database
7
+ include DatabaseMethods
8
+ set_adapter_scheme :oracle
9
+
10
+ # ORA-00028: your session has been killed
11
+ # ORA-01012: not logged on
12
+ # ORA-03113: end-of-file on communication channel
13
+ # ORA-03114: not connected to ORACLE
14
+ CONNECTION_ERROR_CODES = [ 28, 1012, 3113, 3114 ]
15
+
16
+ def connect(server)
17
+ opts = server_opts(server)
18
+ if opts[:database]
19
+ dbname = opts[:host] ? \
20
+ "//#{opts[:host]}#{":#{opts[:port]}" if opts[:port]}/#{opts[:database]}" : opts[:database]
21
+ else
22
+ dbname = opts[:host]
23
+ end
24
+ conn = OCI8.new(opts[:user], opts[:password], dbname, opts[:privilege])
25
+ conn.autocommit = true
26
+ conn.non_blocking = true
27
+ conn
28
+ end
29
+
30
+ def dataset(opts = nil)
31
+ Oracle::Dataset.new(self, opts)
32
+ end
33
+
34
+ def schema_parse_table(table, opts={})
35
+ ds = dataset
36
+ ds.identifier_output_method = :downcase
37
+ schema, table = schema_and_table(table)
38
+ table_schema = []
39
+ metadata = transaction(opts){|conn| conn.describe_table(table.to_s)}
40
+ metadata.columns.each do |column|
41
+ table_schema << [
42
+ column.name.downcase.to_sym,
43
+ {
44
+ :type => column.data_type,
45
+ :db_type => column.type_string.split(' ')[0],
46
+ :type_string => column.type_string,
47
+ :charset_form => column.charset_form,
48
+ :char_used => column.char_used?,
49
+ :char_size => column.char_size,
50
+ :data_size => column.data_size,
51
+ :precision => column.precision,
52
+ :scale => column.scale,
53
+ :fsprecision => column.fsprecision,
54
+ :lfprecision => column.lfprecision,
55
+ :allow_null => column.nullable?
56
+ }
57
+ ]
58
+ end
59
+ table_schema
60
+ end
61
+
62
+ def execute(sql, opts={})
63
+ log_info(sql)
64
+ synchronize(opts[:server]) do |conn|
65
+ begin
66
+ r = conn.exec(sql)
67
+ yield(r) if block_given?
68
+ r
69
+ rescue OCIException => e
70
+ if CONNECTION_ERROR_CODES.include?(e.code)
71
+ raise(Sequel::DatabaseDisconnectError)
72
+ else
73
+ raise
74
+ end
75
+ end
76
+ end
77
+ end
78
+ alias_method :do, :execute
79
+
80
+ def transaction(opts={})
81
+ unless opts.is_a?(Hash)
82
+ Deprecation.deprecate('Passing an argument other than a Hash to Database#transaction', "Use DB.transaction(:server=>#{opts.inspect})")
83
+ opts = {:server=>opts}
84
+ end
85
+ synchronize(opts[:server]) do |conn|
86
+ return yield(conn) if @transactions.include?(Thread.current)
87
+ conn.autocommit = false
88
+ begin
89
+ @transactions << Thread.current
90
+ yield(conn)
91
+ rescue => e
92
+ conn.rollback
93
+ raise e unless Error::Rollback === e
94
+ ensure
95
+ conn.commit unless e
96
+ conn.autocommit = true
97
+ @transactions.delete(Thread.current)
98
+ end
99
+ end
100
+ end
101
+
102
+ private
103
+
104
+ def disconnect_connection(c)
105
+ c.logoff
106
+ end
107
+ end
108
+
109
+ class Dataset < Sequel::Dataset
110
+ include DatasetMethods
111
+
112
+ def fetch_rows(sql, &block)
113
+ execute(sql) do |cursor|
114
+ begin
115
+ @columns = cursor.get_col_names.map{|c| output_identifier(c)}
116
+ while r = cursor.fetch
117
+ row = {}
118
+ r.each_with_index {|v, i| row[@columns[i]] = v unless @columns[i] == :raw_rnum_}
119
+ yield row
120
+ end
121
+ ensure
122
+ cursor.close
123
+ end
124
+ end
125
+ self
126
+ end
127
+
128
+ private
129
+
130
+ def literal_other(v)
131
+ case v
132
+ when OraDate
133
+ literal_time(Time.local(*v.to_a))
134
+ else
135
+ super
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,453 @@
1
+ Sequel.require 'adapters/shared/postgres'
2
+
3
+ begin
4
+ require 'pg'
5
+ SEQUEL_POSTGRES_USES_PG = true
6
+ rescue LoadError => e
7
+ SEQUEL_POSTGRES_USES_PG = false
8
+ begin
9
+ require 'postgres'
10
+ # Attempt to get uniform behavior for the PGconn object no matter
11
+ # if pg, postgres, or postgres-pr is used.
12
+ class PGconn
13
+ unless method_defined?(:escape_string)
14
+ if self.respond_to?(:escape)
15
+ # If there is no escape_string instead method, but there is an
16
+ # escape class method, use that instead.
17
+ def escape_string(str)
18
+ Sequel::Postgres.force_standard_strings ? str.gsub("'", "''") : self.class.escape(str)
19
+ end
20
+ else
21
+ # Raise an error if no valid string escaping method can be found.
22
+ def escape_string(obj)
23
+ raise Sequel::Error, "string escaping not supported with this postgres driver. Try using ruby-pg, ruby-postgres, or postgres-pr."
24
+ end
25
+ end
26
+ end
27
+ unless method_defined?(:escape_bytea)
28
+ if self.respond_to?(:escape_bytea)
29
+ # If there is no escape_bytea instance method, but there is an
30
+ # escape_bytea class method, use that instead.
31
+ def escape_bytea(obj)
32
+ self.class.escape_bytea(obj)
33
+ end
34
+ else
35
+ begin
36
+ require 'postgres-pr/typeconv/conv'
37
+ require 'postgres-pr/typeconv/bytea'
38
+ extend Postgres::Conversion
39
+ # If we are using postgres-pr, use the encode_bytea method from
40
+ # that.
41
+ def escape_bytea(obj)
42
+ self.class.encode_bytea(obj)
43
+ end
44
+ instance_eval{alias unescape_bytea decode_bytea}
45
+ rescue
46
+ # If no valid bytea escaping method can be found, create one that
47
+ # raises an error
48
+ def escape_bytea(obj)
49
+ raise Sequel::Error, "bytea escaping not supported with this postgres driver. Try using ruby-pg, ruby-postgres, or postgres-pr."
50
+ end
51
+ # If no valid bytea unescaping method can be found, create one that
52
+ # raises an error
53
+ def self.unescape_bytea(obj)
54
+ raise Sequel::Error, "bytea unescaping not supported with this postgres driver. Try using ruby-pg, ruby-postgres, or postgres-pr."
55
+ end
56
+ end
57
+ end
58
+ end
59
+ alias_method :finish, :close unless method_defined?(:finish)
60
+ alias_method :async_exec, :exec unless method_defined?(:async_exec)
61
+ unless method_defined?(:block)
62
+ def block(timeout=nil)
63
+ end
64
+ end
65
+ unless defined?(CONNECTION_OK)
66
+ CONNECTION_OK = -1
67
+ end
68
+ end
69
+ class PGresult
70
+ alias_method :nfields, :num_fields unless method_defined?(:nfields)
71
+ alias_method :ntuples, :num_tuples unless method_defined?(:ntuples)
72
+ alias_method :ftype, :type unless method_defined?(:ftype)
73
+ alias_method :fname, :fieldname unless method_defined?(:fname)
74
+ alias_method :cmd_tuples, :cmdtuples unless method_defined?(:cmd_tuples)
75
+ end
76
+ rescue LoadError
77
+ raise e
78
+ end
79
+ end
80
+
81
+ module Sequel
82
+ module Postgres
83
+ CONVERTED_EXCEPTIONS << PGError
84
+
85
+ # Hash with integer keys and proc values for converting PostgreSQL types.
86
+ PG_TYPES = {}
87
+
88
+ # Use a single proc for each type to conserve memory
89
+ PG_TYPE_PROCS = {
90
+ [16] => lambda{|s| s == 't'}, # boolean
91
+ [17] => lambda{|s| ::Sequel::SQL::Blob.new(Adapter.unescape_bytea(s))}, # bytea
92
+ [20, 21, 22, 23, 26] => lambda{|s| s.to_i}, # integer
93
+ [700, 701] => lambda{|s| s.to_f}, # float
94
+ [790, 1700] => lambda{|s| BigDecimal.new(s)}, # numeric
95
+ [1082] => lambda{|s| @use_iso_date_format ? Date.new(*s.split("-").map{|x| x.to_i}) : Sequel.string_to_date(s)}, # date
96
+ [1083, 1266] => lambda{|s| Sequel.string_to_time(s)}, # time
97
+ [1114, 1184] => lambda{|s| Sequel.string_to_datetime(s)}, # timestamp
98
+ }
99
+ PG_TYPE_PROCS.each do |k,v|
100
+ k.each{|n| PG_TYPES[n] = v}
101
+ end
102
+
103
+ @use_iso_date_format = true
104
+
105
+ class << self
106
+ # As an optimization, Sequel sets the date style to ISO, so that PostgreSQL provides
107
+ # the date in a known format that Sequel can parse faster. This can be turned off
108
+ # if you require a date style other than ISO.
109
+ attr_accessor :use_iso_date_format
110
+ end
111
+
112
+ # PGconn subclass for connection specific methods used with the
113
+ # pg, postgres, or postgres-pr driver.
114
+ class Adapter < ::PGconn
115
+ include Sequel::Postgres::AdapterMethods
116
+ self.translate_results = false if respond_to?(:translate_results=)
117
+
118
+ # Apply connection settings for this connection. Current sets
119
+ # the date style to ISO in order make Date object creation in ruby faster,
120
+ # if Postgres.use_iso_date_format is true.
121
+ def apply_connection_settings
122
+ super
123
+ if Postgres.use_iso_date_format
124
+ sql = "SET DateStyle = 'ISO'"
125
+ @db.log_info(sql)
126
+ execute(sql)
127
+ end
128
+ end
129
+
130
+ # Execute the given SQL with this connection. If a block is given,
131
+ # yield the results, otherwise, return the number of changed rows.
132
+ def execute(sql, args=nil)
133
+ q = nil
134
+ begin
135
+ q = args ? async_exec(sql, args) : async_exec(sql)
136
+ rescue PGError
137
+ begin
138
+ s = status
139
+ rescue PGError
140
+ raise(Sequel::DatabaseDisconnectError)
141
+ end
142
+ status_ok = (s == Adapter::CONNECTION_OK)
143
+ status_ok ? raise : raise(Sequel::DatabaseDisconnectError)
144
+ ensure
145
+ block if status_ok
146
+ end
147
+ begin
148
+ block_given? ? yield(q) : q.cmd_tuples
149
+ ensure
150
+ q.clear
151
+ end
152
+ end
153
+
154
+ # Reapply the connection settings if the connection is reset.
155
+ def reset(*args, &block)
156
+ super(*args, &block)
157
+ apply_connection_settings
158
+ end
159
+
160
+ if SEQUEL_POSTGRES_USES_PG
161
+ # Hash of prepared statements for this connection. Keys are
162
+ # string names of the server side prepared statement, and values
163
+ # are SQL strings.
164
+ def prepared_statements
165
+ @prepared_statements ||= {}
166
+ end
167
+ end
168
+
169
+ private
170
+
171
+ # Return the requested values for the given row.
172
+ def single_value(r)
173
+ r.getvalue(0, 0) unless r.nil? || (r.ntuples == 0)
174
+ end
175
+ end
176
+
177
+ # Database class for PostgreSQL databases used with Sequel and the
178
+ # pg, postgres, or postgres-pr driver.
179
+ class Database < Sequel::Database
180
+ include Sequel::Postgres::DatabaseMethods
181
+
182
+ set_adapter_scheme :postgres
183
+
184
+ # Add the primary_keys and primary_key_sequences instance variables,
185
+ # so we can get the correct return values for inserted rows.
186
+ def initialize(*args)
187
+ super
188
+ @primary_keys = {}
189
+ @primary_key_sequences = {}
190
+ end
191
+
192
+ # Connects to the database. In addition to the standard database
193
+ # options, using the :encoding or :charset option changes the
194
+ # client encoding for the connection.
195
+ def connect(server)
196
+ opts = server_opts(server)
197
+ conn = Adapter.connect(
198
+ (opts[:host] unless blank_object?(opts[:host])),
199
+ opts[:port] || 5432,
200
+ nil, '',
201
+ opts[:database],
202
+ opts[:user],
203
+ opts[:password]
204
+ )
205
+ if encoding = opts[:encoding] || opts[:charset]
206
+ if conn.respond_to?(:set_client_encoding)
207
+ conn.set_client_encoding(encoding)
208
+ else
209
+ conn.async_exec("set client_encoding to '#{encoding}'")
210
+ end
211
+ end
212
+ conn.db = self
213
+ conn.apply_connection_settings
214
+ conn
215
+ end
216
+
217
+ # Return instance of Sequel::Postgres::Dataset with the given options.
218
+ def dataset(opts = nil)
219
+ Postgres::Dataset.new(self, opts)
220
+ end
221
+
222
+ # Execute the given SQL with the given args on an available connection.
223
+ def execute(sql, opts={}, &block)
224
+ return execute_prepared_statement(sql, opts, &block) if Symbol === sql
225
+ begin
226
+ log_info(sql, opts[:arguments])
227
+ synchronize(opts[:server]){|conn| conn.execute(sql, opts[:arguments], &block)}
228
+ rescue => e
229
+ log_info(e.message)
230
+ raise_error(e, :classes=>CONVERTED_EXCEPTIONS)
231
+ end
232
+ end
233
+
234
+ # Insert the values into the table and return the primary key (if
235
+ # automatically generated).
236
+ def execute_insert(sql, opts={})
237
+ return execute(sql, opts) if Symbol === sql
238
+ begin
239
+ log_info(sql, opts[:arguments])
240
+ synchronize(opts[:server]) do |conn|
241
+ conn.execute(sql, opts[:arguments])
242
+ insert_result(conn, opts[:table], opts[:values])
243
+ end
244
+ rescue => e
245
+ log_info(e.message)
246
+ raise_error(e, :classes=>CONVERTED_EXCEPTIONS)
247
+ end
248
+ end
249
+
250
+ private
251
+
252
+ # PostgreSQL doesn't need the connection pool to convert exceptions.
253
+ def connection_pool_default_options
254
+ super.merge(:pool_convert_exceptions=>false)
255
+ end
256
+
257
+ # Disconnect given connection
258
+ def disconnect_connection(conn)
259
+ begin
260
+ conn.finish
261
+ rescue PGError
262
+ end
263
+ end
264
+
265
+ # Execute the prepared statement with the given name on an available
266
+ # connection, using the given args. If the connection has not prepared
267
+ # a statement with the given name yet, prepare it. If the connection
268
+ # has prepared a statement with the same name and different SQL,
269
+ # deallocate that statement first and then prepare this statement.
270
+ # If a block is given, yield the result, otherwise, return the number
271
+ # of rows changed. If the :insert option is passed, return the value
272
+ # of the primary key for the last inserted row.
273
+ def execute_prepared_statement(name, opts={})
274
+ ps = prepared_statements[name]
275
+ sql = ps.prepared_sql
276
+ ps_name = name.to_s
277
+ args = opts[:arguments]
278
+ synchronize(opts[:server]) do |conn|
279
+ unless conn.prepared_statements[ps_name] == sql
280
+ if conn.prepared_statements.include?(ps_name)
281
+ s = "DEALLOCATE #{ps_name}"
282
+ log_info(s)
283
+ conn.execute(s) unless conn.prepared_statements[ps_name] == sql
284
+ end
285
+ conn.prepared_statements[ps_name] = sql
286
+ log_info("PREPARE #{ps_name} AS #{sql}")
287
+ conn.prepare(ps_name, sql)
288
+ end
289
+ log_info("EXECUTE #{ps_name}", args)
290
+ q = conn.exec_prepared(ps_name, args)
291
+ if opts[:table] && opts[:values]
292
+ insert_result(conn, opts[:table], opts[:values])
293
+ else
294
+ begin
295
+ block_given? ? yield(q) : q.cmd_tuples
296
+ ensure
297
+ q.clear
298
+ end
299
+ end
300
+ end
301
+ end
302
+ end
303
+
304
+ # Dataset class for PostgreSQL datasets that use the pg, postgres, or
305
+ # postgres-pr driver.
306
+ class Dataset < Sequel::Dataset
307
+ include Sequel::Postgres::DatasetMethods
308
+
309
+ # Yield all rows returned by executing the given SQL and converting
310
+ # the types.
311
+ def fetch_rows(sql)
312
+ cols = []
313
+ execute(sql) do |res|
314
+ res.nfields.times do |fieldnum|
315
+ cols << [fieldnum, PG_TYPES[res.ftype(fieldnum)], output_identifier(res.fname(fieldnum))]
316
+ end
317
+ @columns = cols.map{|c| c.at(2)}
318
+ res.ntuples.times do |recnum|
319
+ converted_rec = {}
320
+ cols.each do |fieldnum, type_proc, fieldsym|
321
+ value = res.getvalue(recnum, fieldnum)
322
+ converted_rec[fieldsym] = (value && type_proc) ? type_proc.call(value) : value
323
+ end
324
+ yield converted_rec
325
+ end
326
+ end
327
+ end
328
+
329
+ if SEQUEL_POSTGRES_USES_PG
330
+
331
+ PREPARED_ARG_PLACEHOLDER = LiteralString.new('$').freeze
332
+
333
+ # PostgreSQL specific argument mapper used for mapping the named
334
+ # argument hash to a array with numbered arguments. Only used with
335
+ # the pg driver.
336
+ module ArgumentMapper
337
+ include Sequel::Dataset::ArgumentMapper
338
+
339
+ protected
340
+
341
+ # Return an array of strings for each of the hash values, inserting
342
+ # them to the correct position in the array.
343
+ def map_to_prepared_args(hash)
344
+ @prepared_args.map{|k| hash[k.to_sym].to_s}
345
+ end
346
+
347
+ private
348
+
349
+ # PostgreSQL most of the time requires type information for each of
350
+ # arguments to a prepared statement. Handle this by allowing the
351
+ # named argument to have a __* suffix, with the * being the type.
352
+ # In the generated SQL, cast the bound argument to that type to
353
+ # elminate ambiguity (and PostgreSQL from raising an exception).
354
+ def prepared_arg(k)
355
+ y, type = k.to_s.split("__")
356
+ if i = @prepared_args.index(y)
357
+ i += 1
358
+ else
359
+ @prepared_args << y
360
+ i = @prepared_args.length
361
+ end
362
+ LiteralString.new("#{prepared_arg_placeholder}#{i}#{"::#{type}" if type}")
363
+ end
364
+ end
365
+
366
+ # Allow use of bind arguments for PostgreSQL using the pg driver.
367
+ module BindArgumentMethods
368
+ include ArgumentMapper
369
+
370
+ private
371
+
372
+ # Execute the given SQL with the stored bind arguments.
373
+ def execute(sql, opts={}, &block)
374
+ super(sql, {:arguments=>bind_arguments}.merge(opts), &block)
375
+ end
376
+
377
+ # Same as execute, explicit due to intricacies of alias and super.
378
+ def execute_dui(sql, opts={}, &block)
379
+ super(sql, {:arguments=>bind_arguments}.merge(opts), &block)
380
+ end
381
+
382
+ # Same as execute, explicit due to intricacies of alias and super.
383
+ def execute_insert(sql, opts={}, &block)
384
+ super(sql, {:arguments=>bind_arguments}.merge(opts), &block)
385
+ end
386
+ end
387
+
388
+ # Allow use of server side prepared statements for PostgreSQL using the
389
+ # pg driver.
390
+ module PreparedStatementMethods
391
+ include BindArgumentMethods
392
+ include ::Sequel::Postgres::DatasetMethods::PreparedStatementMethods
393
+
394
+ private
395
+
396
+ # Execute the stored prepared statement name and the stored bind
397
+ # arguments instead of the SQL given.
398
+ def execute(sql, opts={}, &block)
399
+ super(prepared_statement_name, opts, &block)
400
+ end
401
+
402
+ # Same as execute, explicit due to intricacies of alias and super.
403
+ def execute_dui(sql, opts={}, &block)
404
+ super(prepared_statement_name, opts, &block)
405
+ end
406
+
407
+ # Same as execute, explicit due to intricacies of alias and super.
408
+ def execute_insert(sql, opts={}, &block)
409
+ super(prepared_statement_name, opts, &block)
410
+ end
411
+ end
412
+
413
+ # Execute the given type of statement with the hash of values.
414
+ def call(type, hash, values=nil, &block)
415
+ ps = to_prepared_statement(type, values)
416
+ ps.extend(BindArgumentMethods)
417
+ ps.call(hash, &block)
418
+ end
419
+
420
+ # Prepare the given type of statement with the given name, and store
421
+ # it in the database to be called later.
422
+ def prepare(type, name=nil, values=nil)
423
+ ps = to_prepared_statement(type, values)
424
+ ps.extend(PreparedStatementMethods)
425
+ if name
426
+ ps.prepared_statement_name = name
427
+ db.prepared_statements[name] = ps
428
+ end
429
+ ps
430
+ end
431
+
432
+ private
433
+
434
+ # PostgreSQL uses $N for placeholders instead of ?, so use a $
435
+ # as the placeholder.
436
+ def prepared_arg_placeholder
437
+ PREPARED_ARG_PLACEHOLDER
438
+ end
439
+ end
440
+
441
+ private
442
+
443
+ def literal_blob(v)
444
+ db.synchronize{|c| "'#{c.escape_bytea(v)}'"}
445
+ end
446
+
447
+ def literal_string(v)
448
+ db.synchronize{|c| "'#{c.escape_string(v)}'"}
449
+ end
450
+
451
+ end
452
+ end
453
+ end