epugh-sequel 0.0.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.
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