viking-sequel 3.10.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 (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