viking-sequel 3.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (237) hide show
  1. data/CHANGELOG +3134 -0
  2. data/COPYING +19 -0
  3. data/README.rdoc +723 -0
  4. data/Rakefile +193 -0
  5. data/bin/sequel +196 -0
  6. data/doc/advanced_associations.rdoc +644 -0
  7. data/doc/cheat_sheet.rdoc +218 -0
  8. data/doc/dataset_basics.rdoc +106 -0
  9. data/doc/dataset_filtering.rdoc +158 -0
  10. data/doc/opening_databases.rdoc +296 -0
  11. data/doc/prepared_statements.rdoc +104 -0
  12. data/doc/reflection.rdoc +84 -0
  13. data/doc/release_notes/1.0.txt +38 -0
  14. data/doc/release_notes/1.1.txt +143 -0
  15. data/doc/release_notes/1.3.txt +101 -0
  16. data/doc/release_notes/1.4.0.txt +53 -0
  17. data/doc/release_notes/1.5.0.txt +155 -0
  18. data/doc/release_notes/2.0.0.txt +298 -0
  19. data/doc/release_notes/2.1.0.txt +271 -0
  20. data/doc/release_notes/2.10.0.txt +328 -0
  21. data/doc/release_notes/2.11.0.txt +215 -0
  22. data/doc/release_notes/2.12.0.txt +534 -0
  23. data/doc/release_notes/2.2.0.txt +253 -0
  24. data/doc/release_notes/2.3.0.txt +88 -0
  25. data/doc/release_notes/2.4.0.txt +106 -0
  26. data/doc/release_notes/2.5.0.txt +137 -0
  27. data/doc/release_notes/2.6.0.txt +157 -0
  28. data/doc/release_notes/2.7.0.txt +166 -0
  29. data/doc/release_notes/2.8.0.txt +171 -0
  30. data/doc/release_notes/2.9.0.txt +97 -0
  31. data/doc/release_notes/3.0.0.txt +221 -0
  32. data/doc/release_notes/3.1.0.txt +406 -0
  33. data/doc/release_notes/3.10.0.txt +286 -0
  34. data/doc/release_notes/3.2.0.txt +268 -0
  35. data/doc/release_notes/3.3.0.txt +192 -0
  36. data/doc/release_notes/3.4.0.txt +325 -0
  37. data/doc/release_notes/3.5.0.txt +510 -0
  38. data/doc/release_notes/3.6.0.txt +366 -0
  39. data/doc/release_notes/3.7.0.txt +179 -0
  40. data/doc/release_notes/3.8.0.txt +151 -0
  41. data/doc/release_notes/3.9.0.txt +233 -0
  42. data/doc/schema.rdoc +36 -0
  43. data/doc/sharding.rdoc +113 -0
  44. data/doc/virtual_rows.rdoc +205 -0
  45. data/lib/sequel.rb +1 -0
  46. data/lib/sequel/adapters/ado.rb +90 -0
  47. data/lib/sequel/adapters/ado/mssql.rb +30 -0
  48. data/lib/sequel/adapters/amalgalite.rb +176 -0
  49. data/lib/sequel/adapters/db2.rb +139 -0
  50. data/lib/sequel/adapters/dbi.rb +113 -0
  51. data/lib/sequel/adapters/do.rb +188 -0
  52. data/lib/sequel/adapters/do/mysql.rb +49 -0
  53. data/lib/sequel/adapters/do/postgres.rb +91 -0
  54. data/lib/sequel/adapters/do/sqlite.rb +40 -0
  55. data/lib/sequel/adapters/firebird.rb +283 -0
  56. data/lib/sequel/adapters/informix.rb +77 -0
  57. data/lib/sequel/adapters/jdbc.rb +587 -0
  58. data/lib/sequel/adapters/jdbc/as400.rb +58 -0
  59. data/lib/sequel/adapters/jdbc/h2.rb +133 -0
  60. data/lib/sequel/adapters/jdbc/mssql.rb +57 -0
  61. data/lib/sequel/adapters/jdbc/mysql.rb +78 -0
  62. data/lib/sequel/adapters/jdbc/oracle.rb +50 -0
  63. data/lib/sequel/adapters/jdbc/postgresql.rb +108 -0
  64. data/lib/sequel/adapters/jdbc/sqlite.rb +55 -0
  65. data/lib/sequel/adapters/mysql.rb +421 -0
  66. data/lib/sequel/adapters/odbc.rb +143 -0
  67. data/lib/sequel/adapters/odbc/mssql.rb +42 -0
  68. data/lib/sequel/adapters/openbase.rb +64 -0
  69. data/lib/sequel/adapters/oracle.rb +131 -0
  70. data/lib/sequel/adapters/postgres.rb +504 -0
  71. data/lib/sequel/adapters/shared/mssql.rb +490 -0
  72. data/lib/sequel/adapters/shared/mysql.rb +498 -0
  73. data/lib/sequel/adapters/shared/oracle.rb +195 -0
  74. data/lib/sequel/adapters/shared/postgres.rb +830 -0
  75. data/lib/sequel/adapters/shared/progress.rb +44 -0
  76. data/lib/sequel/adapters/shared/sqlite.rb +389 -0
  77. data/lib/sequel/adapters/sqlite.rb +224 -0
  78. data/lib/sequel/adapters/utils/stored_procedures.rb +84 -0
  79. data/lib/sequel/connection_pool.rb +99 -0
  80. data/lib/sequel/connection_pool/sharded_single.rb +84 -0
  81. data/lib/sequel/connection_pool/sharded_threaded.rb +211 -0
  82. data/lib/sequel/connection_pool/single.rb +29 -0
  83. data/lib/sequel/connection_pool/threaded.rb +150 -0
  84. data/lib/sequel/core.rb +293 -0
  85. data/lib/sequel/core_sql.rb +241 -0
  86. data/lib/sequel/database.rb +1079 -0
  87. data/lib/sequel/database/schema_generator.rb +327 -0
  88. data/lib/sequel/database/schema_methods.rb +203 -0
  89. data/lib/sequel/database/schema_sql.rb +320 -0
  90. data/lib/sequel/dataset.rb +32 -0
  91. data/lib/sequel/dataset/actions.rb +441 -0
  92. data/lib/sequel/dataset/features.rb +86 -0
  93. data/lib/sequel/dataset/graph.rb +254 -0
  94. data/lib/sequel/dataset/misc.rb +119 -0
  95. data/lib/sequel/dataset/mutation.rb +64 -0
  96. data/lib/sequel/dataset/prepared_statements.rb +227 -0
  97. data/lib/sequel/dataset/query.rb +709 -0
  98. data/lib/sequel/dataset/sql.rb +996 -0
  99. data/lib/sequel/exceptions.rb +51 -0
  100. data/lib/sequel/extensions/blank.rb +43 -0
  101. data/lib/sequel/extensions/inflector.rb +242 -0
  102. data/lib/sequel/extensions/looser_typecasting.rb +21 -0
  103. data/lib/sequel/extensions/migration.rb +239 -0
  104. data/lib/sequel/extensions/named_timezones.rb +61 -0
  105. data/lib/sequel/extensions/pagination.rb +100 -0
  106. data/lib/sequel/extensions/pretty_table.rb +82 -0
  107. data/lib/sequel/extensions/query.rb +52 -0
  108. data/lib/sequel/extensions/schema_dumper.rb +271 -0
  109. data/lib/sequel/extensions/sql_expr.rb +122 -0
  110. data/lib/sequel/extensions/string_date_time.rb +46 -0
  111. data/lib/sequel/extensions/thread_local_timezones.rb +48 -0
  112. data/lib/sequel/metaprogramming.rb +9 -0
  113. data/lib/sequel/model.rb +120 -0
  114. data/lib/sequel/model/associations.rb +1514 -0
  115. data/lib/sequel/model/base.rb +1069 -0
  116. data/lib/sequel/model/default_inflections.rb +45 -0
  117. data/lib/sequel/model/errors.rb +39 -0
  118. data/lib/sequel/model/exceptions.rb +21 -0
  119. data/lib/sequel/model/inflections.rb +162 -0
  120. data/lib/sequel/model/plugins.rb +70 -0
  121. data/lib/sequel/plugins/active_model.rb +59 -0
  122. data/lib/sequel/plugins/association_dependencies.rb +103 -0
  123. data/lib/sequel/plugins/association_proxies.rb +41 -0
  124. data/lib/sequel/plugins/boolean_readers.rb +53 -0
  125. data/lib/sequel/plugins/caching.rb +141 -0
  126. data/lib/sequel/plugins/class_table_inheritance.rb +214 -0
  127. data/lib/sequel/plugins/composition.rb +138 -0
  128. data/lib/sequel/plugins/force_encoding.rb +72 -0
  129. data/lib/sequel/plugins/hook_class_methods.rb +126 -0
  130. data/lib/sequel/plugins/identity_map.rb +116 -0
  131. data/lib/sequel/plugins/instance_filters.rb +98 -0
  132. data/lib/sequel/plugins/instance_hooks.rb +57 -0
  133. data/lib/sequel/plugins/lazy_attributes.rb +77 -0
  134. data/lib/sequel/plugins/many_through_many.rb +208 -0
  135. data/lib/sequel/plugins/nested_attributes.rb +206 -0
  136. data/lib/sequel/plugins/optimistic_locking.rb +81 -0
  137. data/lib/sequel/plugins/rcte_tree.rb +281 -0
  138. data/lib/sequel/plugins/schema.rb +66 -0
  139. data/lib/sequel/plugins/serialization.rb +166 -0
  140. data/lib/sequel/plugins/single_table_inheritance.rb +74 -0
  141. data/lib/sequel/plugins/subclasses.rb +45 -0
  142. data/lib/sequel/plugins/tactical_eager_loading.rb +61 -0
  143. data/lib/sequel/plugins/timestamps.rb +87 -0
  144. data/lib/sequel/plugins/touch.rb +118 -0
  145. data/lib/sequel/plugins/typecast_on_load.rb +72 -0
  146. data/lib/sequel/plugins/validation_class_methods.rb +405 -0
  147. data/lib/sequel/plugins/validation_helpers.rb +223 -0
  148. data/lib/sequel/sql.rb +1020 -0
  149. data/lib/sequel/timezones.rb +161 -0
  150. data/lib/sequel/version.rb +12 -0
  151. data/lib/sequel_core.rb +1 -0
  152. data/lib/sequel_model.rb +1 -0
  153. data/spec/adapters/firebird_spec.rb +407 -0
  154. data/spec/adapters/informix_spec.rb +97 -0
  155. data/spec/adapters/mssql_spec.rb +403 -0
  156. data/spec/adapters/mysql_spec.rb +1019 -0
  157. data/spec/adapters/oracle_spec.rb +286 -0
  158. data/spec/adapters/postgres_spec.rb +969 -0
  159. data/spec/adapters/spec_helper.rb +51 -0
  160. data/spec/adapters/sqlite_spec.rb +432 -0
  161. data/spec/core/connection_pool_spec.rb +808 -0
  162. data/spec/core/core_sql_spec.rb +417 -0
  163. data/spec/core/database_spec.rb +1662 -0
  164. data/spec/core/dataset_spec.rb +3827 -0
  165. data/spec/core/expression_filters_spec.rb +595 -0
  166. data/spec/core/object_graph_spec.rb +296 -0
  167. data/spec/core/schema_generator_spec.rb +159 -0
  168. data/spec/core/schema_spec.rb +830 -0
  169. data/spec/core/spec_helper.rb +56 -0
  170. data/spec/core/version_spec.rb +7 -0
  171. data/spec/extensions/active_model_spec.rb +76 -0
  172. data/spec/extensions/association_dependencies_spec.rb +127 -0
  173. data/spec/extensions/association_proxies_spec.rb +50 -0
  174. data/spec/extensions/blank_spec.rb +67 -0
  175. data/spec/extensions/boolean_readers_spec.rb +92 -0
  176. data/spec/extensions/caching_spec.rb +250 -0
  177. data/spec/extensions/class_table_inheritance_spec.rb +252 -0
  178. data/spec/extensions/composition_spec.rb +194 -0
  179. data/spec/extensions/force_encoding_spec.rb +117 -0
  180. data/spec/extensions/hook_class_methods_spec.rb +470 -0
  181. data/spec/extensions/identity_map_spec.rb +202 -0
  182. data/spec/extensions/inflector_spec.rb +181 -0
  183. data/spec/extensions/instance_filters_spec.rb +55 -0
  184. data/spec/extensions/instance_hooks_spec.rb +133 -0
  185. data/spec/extensions/lazy_attributes_spec.rb +153 -0
  186. data/spec/extensions/looser_typecasting_spec.rb +39 -0
  187. data/spec/extensions/many_through_many_spec.rb +884 -0
  188. data/spec/extensions/migration_spec.rb +332 -0
  189. data/spec/extensions/named_timezones_spec.rb +72 -0
  190. data/spec/extensions/nested_attributes_spec.rb +396 -0
  191. data/spec/extensions/optimistic_locking_spec.rb +100 -0
  192. data/spec/extensions/pagination_spec.rb +99 -0
  193. data/spec/extensions/pretty_table_spec.rb +91 -0
  194. data/spec/extensions/query_spec.rb +85 -0
  195. data/spec/extensions/rcte_tree_spec.rb +205 -0
  196. data/spec/extensions/schema_dumper_spec.rb +357 -0
  197. data/spec/extensions/schema_spec.rb +127 -0
  198. data/spec/extensions/serialization_spec.rb +209 -0
  199. data/spec/extensions/single_table_inheritance_spec.rb +96 -0
  200. data/spec/extensions/spec_helper.rb +91 -0
  201. data/spec/extensions/sql_expr_spec.rb +89 -0
  202. data/spec/extensions/string_date_time_spec.rb +93 -0
  203. data/spec/extensions/subclasses_spec.rb +52 -0
  204. data/spec/extensions/tactical_eager_loading_spec.rb +65 -0
  205. data/spec/extensions/thread_local_timezones_spec.rb +45 -0
  206. data/spec/extensions/timestamps_spec.rb +150 -0
  207. data/spec/extensions/touch_spec.rb +155 -0
  208. data/spec/extensions/typecast_on_load_spec.rb +69 -0
  209. data/spec/extensions/validation_class_methods_spec.rb +984 -0
  210. data/spec/extensions/validation_helpers_spec.rb +438 -0
  211. data/spec/integration/associations_test.rb +281 -0
  212. data/spec/integration/database_test.rb +26 -0
  213. data/spec/integration/dataset_test.rb +963 -0
  214. data/spec/integration/eager_loader_test.rb +734 -0
  215. data/spec/integration/model_test.rb +130 -0
  216. data/spec/integration/plugin_test.rb +814 -0
  217. data/spec/integration/prepared_statement_test.rb +213 -0
  218. data/spec/integration/schema_test.rb +361 -0
  219. data/spec/integration/spec_helper.rb +73 -0
  220. data/spec/integration/timezone_test.rb +55 -0
  221. data/spec/integration/transaction_test.rb +122 -0
  222. data/spec/integration/type_test.rb +96 -0
  223. data/spec/model/association_reflection_spec.rb +175 -0
  224. data/spec/model/associations_spec.rb +2633 -0
  225. data/spec/model/base_spec.rb +418 -0
  226. data/spec/model/dataset_methods_spec.rb +78 -0
  227. data/spec/model/eager_loading_spec.rb +1391 -0
  228. data/spec/model/hooks_spec.rb +240 -0
  229. data/spec/model/inflector_spec.rb +26 -0
  230. data/spec/model/model_spec.rb +593 -0
  231. data/spec/model/plugins_spec.rb +236 -0
  232. data/spec/model/record_spec.rb +1500 -0
  233. data/spec/model/spec_helper.rb +97 -0
  234. data/spec/model/validations_spec.rb +153 -0
  235. data/spec/rcov.opts +6 -0
  236. data/spec/spec_config.rb.example +10 -0
  237. metadata +346 -0
@@ -0,0 +1,143 @@
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
13
+ case @opts[:db_type]
14
+ when 'mssql'
15
+ Sequel.ts_require 'adapters/odbc/mssql'
16
+ extend Sequel::ODBC::MSSQL::DatabaseMethods
17
+ when 'progress'
18
+ Sequel.ts_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
+ def execute(sql, opts={})
48
+ synchronize(opts[:server]) do |conn|
49
+ begin
50
+ r = log_yield(sql){conn.run(sql)}
51
+ yield(r) if block_given?
52
+ rescue ::ODBC::Error, ArgumentError => e
53
+ raise_error(e)
54
+ ensure
55
+ r.drop if r
56
+ end
57
+ nil
58
+ end
59
+ end
60
+
61
+ def execute_dui(sql, opts={})
62
+ synchronize(opts[:server]) do |conn|
63
+ begin
64
+ log_yield(sql){conn.do(sql)}
65
+ rescue ::ODBC::Error, ArgumentError => e
66
+ raise_error(e)
67
+ end
68
+ end
69
+ end
70
+ alias do execute_dui
71
+
72
+ private
73
+
74
+ def connection_execute_method
75
+ :do
76
+ end
77
+
78
+ def disconnect_connection(c)
79
+ c.disconnect
80
+ end
81
+ end
82
+
83
+ class Dataset < Sequel::Dataset
84
+ BOOL_TRUE = '1'.freeze
85
+ BOOL_FALSE = '0'.freeze
86
+ ODBC_DATE_FORMAT = "{d '%Y-%m-%d'}".freeze
87
+ TIMESTAMP_FORMAT="{ts '%Y-%m-%d %H:%M:%S'}".freeze
88
+
89
+ def fetch_rows(sql, &block)
90
+ execute(sql) do |s|
91
+ i = -1
92
+ cols = s.columns(true).map{|c| [output_identifier(c.name), i+=1]}
93
+ @columns = cols.map{|c| c.at(0)}
94
+ if rows = s.fetch_all
95
+ rows.each do |row|
96
+ hash = {}
97
+ cols.each{|n,i| hash[n] = convert_odbc_value(row[i])}
98
+ yield hash
99
+ end
100
+ end
101
+ end
102
+ self
103
+ end
104
+
105
+ private
106
+
107
+ def convert_odbc_value(v)
108
+ # When fetching a result set, the Ruby ODBC driver converts all ODBC
109
+ # SQL types to an equivalent Ruby type; with the exception of
110
+ # SQL_TYPE_DATE, SQL_TYPE_TIME and SQL_TYPE_TIMESTAMP.
111
+ #
112
+ # The conversions below are consistent with the mappings in
113
+ # ODBCColumn#mapSqlTypeToGenericType and Column#klass.
114
+ case v
115
+ when ::ODBC::TimeStamp
116
+ Sequel.database_to_application_timestamp([v.year, v.month, v.day, v.hour, v.minute, v.second])
117
+ when ::ODBC::Time
118
+ Sequel.database_to_application_timestamp([now.year, now.month, now.day, v.hour, v.minute, v.second])
119
+ when ::ODBC::Date
120
+ Date.new(v.year, v.month, v.day)
121
+ else
122
+ v
123
+ end
124
+ end
125
+
126
+ def default_timestamp_format
127
+ TIMESTAMP_FORMAT
128
+ end
129
+
130
+ def literal_date(v)
131
+ v.strftime(ODBC_DATE_FORMAT)
132
+ end
133
+
134
+ def literal_false
135
+ BOOL_FALSE
136
+ end
137
+
138
+ def literal_true
139
+ BOOL_TRUE
140
+ end
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,42 @@
1
+ Sequel.require 'adapters/shared/mssql'
2
+
3
+ module Sequel
4
+ module ODBC
5
+ # Database and Dataset instance methods for MSSQL specific
6
+ # support via ODBC.
7
+ module MSSQL
8
+ module DatabaseMethods
9
+ include Sequel::MSSQL::DatabaseMethods
10
+ LAST_INSERT_ID_SQL='SELECT SCOPE_IDENTITY()'
11
+
12
+ # Return an instance of Sequel::ODBC::MSSQL::Dataset with the given opts.
13
+ def dataset(opts=nil)
14
+ Sequel::ODBC::MSSQL::Dataset.new(self, opts)
15
+ end
16
+
17
+ # Return the last inserted identity value.
18
+ def execute_insert(sql, opts={})
19
+ synchronize(opts[:server]) do |conn|
20
+ begin
21
+ log_yield(sql){conn.do(sql)}
22
+ begin
23
+ s = log_yield(LAST_INSERT_ID_SQL){conn.run(LAST_INSERT_ID_SQL)}
24
+ if (rows = s.fetch_all) and (row = rows.first)
25
+ Integer(row.first)
26
+ end
27
+ ensure
28
+ s.drop if s
29
+ end
30
+ rescue ::ODBC::Error => e
31
+ raise_error(e)
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ class Dataset < ODBC::Dataset
38
+ include Sequel::MSSQL::DatasetMethods
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,64 @@
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
+ synchronize(opts[:server]) do |conn|
24
+ r = log_yield(sql){conn.execute(sql)}
25
+ yield(r) if block_given?
26
+ r
27
+ end
28
+ end
29
+ alias_method :do, :execute
30
+
31
+ private
32
+
33
+ def disconnect_connection(c)
34
+ c.disconnect
35
+ end
36
+ end
37
+
38
+ class Dataset < Sequel::Dataset
39
+ SELECT_CLAUSE_METHODS = clause_methods(:select, %w'distinct columns from join where group having compounds order limit')
40
+
41
+ def fetch_rows(sql)
42
+ execute(sql) do |result|
43
+ begin
44
+ @columns = result.column_infos.map{|c| output_identifier(c.name)}
45
+ result.each do |r|
46
+ row = {}
47
+ r.each_with_index {|v, i| row[@columns[i]] = v}
48
+ yield row
49
+ end
50
+ ensure
51
+ # result.close
52
+ end
53
+ end
54
+ self
55
+ end
56
+
57
+ private
58
+
59
+ def select_clause_methods
60
+ SELECT_CLAUSE_METHODS
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,131 @@
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
+ synchronize(opts[:server]) do |conn|
64
+ begin
65
+ r = log_yield(sql){conn.exec(sql)}
66
+ yield(r) if block_given?
67
+ r
68
+ rescue OCIException => e
69
+ raise_error(e, :disconnect=>CONNECTION_ERROR_CODES.include?(e.code))
70
+ end
71
+ end
72
+ end
73
+ alias_method :do, :execute
74
+
75
+ private
76
+
77
+ def begin_transaction(conn)
78
+ log_yield(TRANSACTION_BEGIN){conn.autocommit = false}
79
+ conn
80
+ end
81
+
82
+ def commit_transaction(conn)
83
+ log_yield(TRANSACTION_COMMIT){conn.commit}
84
+ end
85
+
86
+ def disconnect_connection(c)
87
+ c.logoff
88
+ end
89
+
90
+ def remove_transaction(conn)
91
+ conn.autocommit = true if conn
92
+ super
93
+ end
94
+
95
+ def rollback_transaction(conn)
96
+ log_yield(TRANSACTION_ROLLBACK){conn.rollback}
97
+ end
98
+ end
99
+
100
+ class Dataset < Sequel::Dataset
101
+ include DatasetMethods
102
+
103
+ def fetch_rows(sql, &block)
104
+ execute(sql) do |cursor|
105
+ begin
106
+ @columns = cursor.get_col_names.map{|c| output_identifier(c)}
107
+ while r = cursor.fetch
108
+ row = {}
109
+ r.each_with_index {|v, i| row[@columns[i]] = v unless @columns[i] == :raw_rnum_}
110
+ yield row
111
+ end
112
+ ensure
113
+ cursor.close
114
+ end
115
+ end
116
+ self
117
+ end
118
+
119
+ private
120
+
121
+ def literal_other(v)
122
+ case v
123
+ when OraDate
124
+ literal(Sequel.database_to_application_timestamp(v))
125
+ else
126
+ super
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,504 @@
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
+ unless method_defined?(:status)
69
+ def status
70
+ CONNECTION_OK
71
+ end
72
+ end
73
+ end
74
+ class PGresult
75
+ alias_method :nfields, :num_fields unless method_defined?(:nfields)
76
+ alias_method :ntuples, :num_tuples unless method_defined?(:ntuples)
77
+ alias_method :ftype, :type unless method_defined?(:ftype)
78
+ alias_method :fname, :fieldname unless method_defined?(:fname)
79
+ alias_method :cmd_tuples, :cmdtuples unless method_defined?(:cmd_tuples)
80
+ end
81
+ rescue LoadError
82
+ raise e
83
+ end
84
+ end
85
+
86
+ module Sequel
87
+ Dataset::NON_SQL_OPTIONS << :cursor
88
+ module Postgres
89
+ CONVERTED_EXCEPTIONS << PGError
90
+
91
+ # Hash with integer keys and proc values for converting PostgreSQL types.
92
+ PG_TYPES = {}
93
+
94
+ # Use a single proc for each type to conserve memory
95
+ PG_TYPE_PROCS = {
96
+ [16] => lambda{|s| s == 't'}, # boolean
97
+ [17] => lambda{|s| ::Sequel::SQL::Blob.new(Adapter.unescape_bytea(s))}, # bytea
98
+ [20, 21, 22, 23, 26] => lambda{|s| s.to_i}, # integer
99
+ [700, 701] => lambda{|s| s.to_f}, # float
100
+ [790, 1700] => lambda{|s| BigDecimal.new(s)}, # numeric
101
+ [1082] => lambda{|s| @use_iso_date_format ? Date.new(*s.split("-").map{|x| x.to_i}) : Sequel.string_to_date(s)}, # date
102
+ [1083, 1266] => lambda{|s| Sequel.string_to_time(s)}, # time
103
+ [1114, 1184] => lambda{|s| Sequel.database_to_application_timestamp(s)}, # timestamp
104
+ }
105
+ PG_TYPE_PROCS.each do |k,v|
106
+ k.each{|n| PG_TYPES[n] = v}
107
+ end
108
+
109
+ @use_iso_date_format = true
110
+
111
+ class << self
112
+ # As an optimization, Sequel sets the date style to ISO, so that PostgreSQL provides
113
+ # the date in a known format that Sequel can parse faster. This can be turned off
114
+ # if you require a date style other than ISO.
115
+ attr_accessor :use_iso_date_format
116
+ end
117
+
118
+ # PGconn subclass for connection specific methods used with the
119
+ # pg, postgres, or postgres-pr driver.
120
+ class Adapter < ::PGconn
121
+ include Sequel::Postgres::AdapterMethods
122
+ self.translate_results = false if respond_to?(:translate_results=)
123
+
124
+ # Hash of prepared statements for this connection. Keys are
125
+ # string names of the server side prepared statement, and values
126
+ # are SQL strings.
127
+ attr_reader(:prepared_statements) if SEQUEL_POSTGRES_USES_PG
128
+
129
+ # Apply connection settings for this connection. Current sets
130
+ # the date style to ISO in order make Date object creation in ruby faster,
131
+ # if Postgres.use_iso_date_format is true.
132
+ def apply_connection_settings
133
+ super
134
+ if Postgres.use_iso_date_format
135
+ sql = "SET DateStyle = 'ISO'"
136
+ execute(sql)
137
+ end
138
+ @prepared_statements = {} if SEQUEL_POSTGRES_USES_PG
139
+ end
140
+
141
+ # Raise a Sequel::DatabaseDisconnectError if a PGError is raised and
142
+ # the connection status cannot be determined or it is not OK.
143
+ def check_disconnect_errors
144
+ begin
145
+ yield
146
+ rescue PGError =>e
147
+ begin
148
+ s = status
149
+ rescue PGError
150
+ raise Sequel.convert_exception_class(e, Sequel::DatabaseDisconnectError)
151
+ end
152
+ status_ok = (s == Adapter::CONNECTION_OK)
153
+ status_ok ? raise : raise(Sequel.convert_exception_class(e, Sequel::DatabaseDisconnectError))
154
+ ensure
155
+ block if status_ok
156
+ end
157
+ end
158
+
159
+ # Execute the given SQL with this connection. If a block is given,
160
+ # yield the results, otherwise, return the number of changed rows.
161
+ def execute(sql, args=nil)
162
+ q = check_disconnect_errors{@db.log_yield(sql, args){args ? async_exec(sql, args) : async_exec(sql)}}
163
+ begin
164
+ block_given? ? yield(q) : q.cmd_tuples
165
+ ensure
166
+ q.clear
167
+ end
168
+ end
169
+
170
+ private
171
+
172
+ # Return the requested values for the given row.
173
+ def single_value(r)
174
+ r.getvalue(0, 0) unless r.nil? || (r.ntuples == 0)
175
+ end
176
+ end
177
+
178
+ # Database class for PostgreSQL databases used with Sequel and the
179
+ # pg, postgres, or postgres-pr driver.
180
+ class Database < Sequel::Database
181
+ include Sequel::Postgres::DatabaseMethods
182
+
183
+ set_adapter_scheme :postgres
184
+
185
+ # Add the primary_keys and primary_key_sequences instance variables,
186
+ # so we can get the correct return values for inserted rows.
187
+ def initialize(*args)
188
+ super
189
+ @primary_keys = {}
190
+ @primary_key_sequences = {}
191
+ end
192
+
193
+ # Connects to the database. In addition to the standard database
194
+ # options, using the :encoding or :charset option changes the
195
+ # client encoding for the connection.
196
+ def connect(server)
197
+ opts = server_opts(server)
198
+ conn = Adapter.connect(
199
+ (opts[:host] unless blank_object?(opts[:host])),
200
+ opts[:port] || 5432,
201
+ nil, '',
202
+ opts[:database],
203
+ opts[:user],
204
+ opts[:password]
205
+ )
206
+ if encoding = opts[:encoding] || opts[:charset]
207
+ if conn.respond_to?(:set_client_encoding)
208
+ conn.set_client_encoding(encoding)
209
+ else
210
+ conn.async_exec("set client_encoding to '#{encoding}'")
211
+ end
212
+ end
213
+ conn.db = self
214
+ conn.apply_connection_settings
215
+ conn
216
+ end
217
+
218
+ # Return instance of Sequel::Postgres::Dataset with the given options.
219
+ def dataset(opts = nil)
220
+ Postgres::Dataset.new(self, opts)
221
+ end
222
+
223
+ # Execute the given SQL with the given args on an available connection.
224
+ def execute(sql, opts={}, &block)
225
+ check_database_errors do
226
+ return execute_prepared_statement(sql, opts, &block) if Symbol === sql
227
+ synchronize(opts[:server]){|conn| conn.execute(sql, opts[:arguments], &block)}
228
+ end
229
+ end
230
+
231
+ # Insert the values into the table and return the primary key (if
232
+ # automatically generated).
233
+ def execute_insert(sql, opts={})
234
+ return execute(sql, opts) if Symbol === sql
235
+ check_database_errors do
236
+ synchronize(opts[:server]) do |conn|
237
+ conn.execute(sql, opts[:arguments])
238
+ insert_result(conn, opts[:table], opts[:values])
239
+ end
240
+ end
241
+ end
242
+
243
+ private
244
+
245
+ # Convert exceptions raised from the block into DatabaseErrors.
246
+ def check_database_errors
247
+ begin
248
+ yield
249
+ rescue => e
250
+ raise_error(e, :classes=>CONVERTED_EXCEPTIONS)
251
+ end
252
+ end
253
+
254
+ # Disconnect given connection
255
+ def disconnect_connection(conn)
256
+ begin
257
+ conn.finish
258
+ rescue PGError
259
+ end
260
+ end
261
+
262
+ # Execute the prepared statement with the given name on an available
263
+ # connection, using the given args. If the connection has not prepared
264
+ # a statement with the given name yet, prepare it. If the connection
265
+ # has prepared a statement with the same name and different SQL,
266
+ # deallocate that statement first and then prepare this statement.
267
+ # If a block is given, yield the result, otherwise, return the number
268
+ # of rows changed. If the :insert option is passed, return the value
269
+ # of the primary key for the last inserted row.
270
+ def execute_prepared_statement(name, opts={})
271
+ ps = prepared_statements[name]
272
+ sql = ps.prepared_sql
273
+ ps_name = name.to_s
274
+ args = opts[:arguments]
275
+ synchronize(opts[:server]) do |conn|
276
+ unless conn.prepared_statements[ps_name] == sql
277
+ if conn.prepared_statements.include?(ps_name)
278
+ conn.execute("DEALLOCATE #{ps_name}") unless conn.prepared_statements[ps_name] == sql
279
+ end
280
+ conn.prepared_statements[ps_name] = sql
281
+ conn.check_disconnect_errors{log_yield("PREPARE #{ps_name} AS #{sql}"){conn.prepare(ps_name, sql)}}
282
+ end
283
+ q = conn.check_disconnect_errors{log_yield("EXECUTE #{ps_name}", args){conn.exec_prepared(ps_name, args)}}
284
+ if opts[:table] && opts[:values]
285
+ insert_result(conn, opts[:table], opts[:values])
286
+ else
287
+ begin
288
+ block_given? ? yield(q) : q.cmd_tuples
289
+ ensure
290
+ q.clear
291
+ end
292
+ end
293
+ end
294
+ end
295
+ end
296
+
297
+ # Dataset class for PostgreSQL datasets that use the pg, postgres, or
298
+ # postgres-pr driver.
299
+ class Dataset < Sequel::Dataset
300
+ include Sequel::Postgres::DatasetMethods
301
+
302
+ # Yield all rows returned by executing the given SQL and converting
303
+ # the types.
304
+ def fetch_rows(sql, &block)
305
+ return cursor_fetch_rows(sql, &block) if @opts[:cursor]
306
+ execute(sql){|res| yield_hash_rows(res, fetch_rows_set_cols(res), &block)}
307
+ end
308
+
309
+ # Uses a cursor for fetching records, instead of fetching the entire result
310
+ # set at once. Can be used to process large datasets without holding
311
+ # all rows in memory (which is what the underlying drivers do
312
+ # by default). Options:
313
+ #
314
+ # * :rows_per_fetch - the number of rows per fetch (default 1000). Higher
315
+ # numbers result in fewer queries but greater memory use.
316
+ #
317
+ # Usage:
318
+ #
319
+ # DB[:huge_table].use_cursor.each{|row| p row}
320
+ # DB[:huge_table].use_cursor(:rows_per_fetch=>10000).each{|row| p row}
321
+ #
322
+ # This is untested with the prepared statement/bound variable support,
323
+ # and unlikely to work with either.
324
+ def use_cursor(opts={})
325
+ clone(:cursor=>{:rows_per_fetch=>1000}.merge(opts))
326
+ end
327
+
328
+ if SEQUEL_POSTGRES_USES_PG
329
+
330
+ PREPARED_ARG_PLACEHOLDER = LiteralString.new('$').freeze
331
+
332
+ # PostgreSQL specific argument mapper used for mapping the named
333
+ # argument hash to a array with numbered arguments. Only used with
334
+ # the pg driver.
335
+ module ArgumentMapper
336
+ include Sequel::Dataset::ArgumentMapper
337
+
338
+ protected
339
+
340
+ # An array of bound variable values for this query, in the correct order.
341
+ def map_to_prepared_args(hash)
342
+ prepared_args.map{|k| hash[k.to_sym]}
343
+ end
344
+
345
+ private
346
+
347
+ # PostgreSQL most of the time requires type information for each of
348
+ # arguments to a prepared statement. Handle this by allowing the
349
+ # named argument to have a __* suffix, with the * being the type.
350
+ # In the generated SQL, cast the bound argument to that type to
351
+ # elminate ambiguity (and PostgreSQL from raising an exception).
352
+ def prepared_arg(k)
353
+ y, type = k.to_s.split("__")
354
+ if i = prepared_args.index(y)
355
+ i += 1
356
+ else
357
+ prepared_args << y
358
+ i = prepared_args.length
359
+ end
360
+ LiteralString.new("#{prepared_arg_placeholder}#{i}#{"::#{type}" if type}")
361
+ end
362
+ end
363
+
364
+ # Allow use of bind arguments for PostgreSQL using the pg driver.
365
+ module BindArgumentMethods
366
+ include ArgumentMapper
367
+
368
+ private
369
+
370
+ # Execute the given SQL with the stored bind arguments.
371
+ def execute(sql, opts={}, &block)
372
+ super(sql, {:arguments=>bind_arguments}.merge(opts), &block)
373
+ end
374
+
375
+ # Same as execute, explicit due to intricacies of alias and super.
376
+ def execute_dui(sql, opts={}, &block)
377
+ super(sql, {:arguments=>bind_arguments}.merge(opts), &block)
378
+ end
379
+
380
+ # Same as execute, explicit due to intricacies of alias and super.
381
+ def execute_insert(sql, opts={}, &block)
382
+ super(sql, {:arguments=>bind_arguments}.merge(opts), &block)
383
+ end
384
+ end
385
+
386
+ # Allow use of server side prepared statements for PostgreSQL using the
387
+ # pg driver.
388
+ module PreparedStatementMethods
389
+ include BindArgumentMethods
390
+ include ::Sequel::Postgres::DatasetMethods::PreparedStatementMethods
391
+
392
+ private
393
+
394
+ # Execute the stored prepared statement name and the stored bind
395
+ # arguments instead of the SQL given.
396
+ def execute(sql, opts={}, &block)
397
+ super(prepared_statement_name, opts, &block)
398
+ end
399
+
400
+ # Same as execute, explicit due to intricacies of alias and super.
401
+ def execute_dui(sql, opts={}, &block)
402
+ super(prepared_statement_name, opts, &block)
403
+ end
404
+
405
+ # Same as execute, explicit due to intricacies of alias and super.
406
+ def execute_insert(sql, opts={}, &block)
407
+ super(prepared_statement_name, opts, &block)
408
+ end
409
+ end
410
+
411
+ # Execute the given type of statement with the hash of values.
412
+ def call(type, bind_vars={}, *values, &block)
413
+ ps = to_prepared_statement(type, values)
414
+ ps.extend(BindArgumentMethods)
415
+ ps.call(bind_vars, &block)
416
+ end
417
+
418
+ # Prepare the given type of statement with the given name, and store
419
+ # it in the database to be called later.
420
+ def prepare(type, name=nil, *values)
421
+ ps = to_prepared_statement(type, values)
422
+ ps.extend(PreparedStatementMethods)
423
+ if name
424
+ ps.prepared_statement_name = name
425
+ db.prepared_statements[name] = ps
426
+ end
427
+ ps
428
+ end
429
+
430
+ private
431
+
432
+ # PostgreSQL uses $N for placeholders instead of ?, so use a $
433
+ # as the placeholder.
434
+ def prepared_arg_placeholder
435
+ PREPARED_ARG_PLACEHOLDER
436
+ end
437
+ end
438
+
439
+ private
440
+
441
+ # Use a cursor to fetch groups of records at a time, yielding them to the block.
442
+ def cursor_fetch_rows(sql, &block)
443
+ server_opts = {:server=>@opts[:server] || :read_only}
444
+ db.transaction(server_opts) do
445
+ begin
446
+ execute_ddl("DECLARE sequel_cursor NO SCROLL CURSOR WITHOUT HOLD FOR #{sql}", server_opts)
447
+ rows_per_fetch = @opts[:cursor][:rows_per_fetch].to_i
448
+ rows_per_fetch = 1000 if rows_per_fetch <= 0
449
+ fetch_sql = "FETCH FORWARD #{rows_per_fetch} FROM sequel_cursor"
450
+ cols = nil
451
+ # Load columns only in the first fetch, so subsequent fetches are faster
452
+ execute(fetch_sql) do |res|
453
+ cols = fetch_rows_set_cols(res)
454
+ yield_hash_rows(res, cols, &block)
455
+ return if res.ntuples < rows_per_fetch
456
+ end
457
+ loop do
458
+ execute(fetch_sql) do |res|
459
+ yield_hash_rows(res, cols, &block)
460
+ return if res.ntuples < rows_per_fetch
461
+ end
462
+ end
463
+ ensure
464
+ execute_ddl("CLOSE sequel_cursor", server_opts)
465
+ end
466
+ end
467
+ end
468
+
469
+ # Set the @columns based on the result set, and return the array of
470
+ # field numers, type conversion procs, and name symbol arrays.
471
+ def fetch_rows_set_cols(res)
472
+ cols = []
473
+ res.nfields.times do |fieldnum|
474
+ cols << [fieldnum, PG_TYPES[res.ftype(fieldnum)], output_identifier(res.fname(fieldnum))]
475
+ end
476
+ @columns = cols.map{|c| c.at(2)}
477
+ cols
478
+ end
479
+
480
+ # Use the driver's escape_bytea
481
+ def literal_blob(v)
482
+ db.synchronize{|c| "'#{c.escape_bytea(v)}'"}
483
+ end
484
+
485
+ # Use the driver's escape_string
486
+ def literal_string(v)
487
+ db.synchronize{|c| "'#{c.escape_string(v)}'"}
488
+ end
489
+
490
+ # For each row in the result set, yield a hash with column name symbol
491
+ # keys and typecasted values.
492
+ def yield_hash_rows(res, cols)
493
+ res.ntuples.times do |recnum|
494
+ converted_rec = {}
495
+ cols.each do |fieldnum, type_proc, fieldsym|
496
+ value = res.getvalue(recnum, fieldnum)
497
+ converted_rec[fieldsym] = (value && type_proc) ? type_proc.call(value) : value
498
+ end
499
+ yield converted_rec
500
+ end
501
+ end
502
+ end
503
+ end
504
+ end