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.
- data/CHANGELOG +3134 -0
- data/COPYING +19 -0
- data/README.rdoc +723 -0
- data/Rakefile +193 -0
- data/bin/sequel +196 -0
- data/doc/advanced_associations.rdoc +644 -0
- data/doc/cheat_sheet.rdoc +218 -0
- data/doc/dataset_basics.rdoc +106 -0
- data/doc/dataset_filtering.rdoc +158 -0
- data/doc/opening_databases.rdoc +296 -0
- data/doc/prepared_statements.rdoc +104 -0
- data/doc/reflection.rdoc +84 -0
- data/doc/release_notes/1.0.txt +38 -0
- data/doc/release_notes/1.1.txt +143 -0
- data/doc/release_notes/1.3.txt +101 -0
- data/doc/release_notes/1.4.0.txt +53 -0
- data/doc/release_notes/1.5.0.txt +155 -0
- data/doc/release_notes/2.0.0.txt +298 -0
- data/doc/release_notes/2.1.0.txt +271 -0
- data/doc/release_notes/2.10.0.txt +328 -0
- data/doc/release_notes/2.11.0.txt +215 -0
- data/doc/release_notes/2.12.0.txt +534 -0
- data/doc/release_notes/2.2.0.txt +253 -0
- data/doc/release_notes/2.3.0.txt +88 -0
- data/doc/release_notes/2.4.0.txt +106 -0
- data/doc/release_notes/2.5.0.txt +137 -0
- data/doc/release_notes/2.6.0.txt +157 -0
- data/doc/release_notes/2.7.0.txt +166 -0
- data/doc/release_notes/2.8.0.txt +171 -0
- data/doc/release_notes/2.9.0.txt +97 -0
- data/doc/release_notes/3.0.0.txt +221 -0
- data/doc/release_notes/3.1.0.txt +406 -0
- data/doc/release_notes/3.10.0.txt +286 -0
- data/doc/release_notes/3.2.0.txt +268 -0
- data/doc/release_notes/3.3.0.txt +192 -0
- data/doc/release_notes/3.4.0.txt +325 -0
- data/doc/release_notes/3.5.0.txt +510 -0
- data/doc/release_notes/3.6.0.txt +366 -0
- data/doc/release_notes/3.7.0.txt +179 -0
- data/doc/release_notes/3.8.0.txt +151 -0
- data/doc/release_notes/3.9.0.txt +233 -0
- data/doc/schema.rdoc +36 -0
- data/doc/sharding.rdoc +113 -0
- data/doc/virtual_rows.rdoc +205 -0
- data/lib/sequel.rb +1 -0
- data/lib/sequel/adapters/ado.rb +90 -0
- data/lib/sequel/adapters/ado/mssql.rb +30 -0
- data/lib/sequel/adapters/amalgalite.rb +176 -0
- data/lib/sequel/adapters/db2.rb +139 -0
- data/lib/sequel/adapters/dbi.rb +113 -0
- data/lib/sequel/adapters/do.rb +188 -0
- data/lib/sequel/adapters/do/mysql.rb +49 -0
- data/lib/sequel/adapters/do/postgres.rb +91 -0
- data/lib/sequel/adapters/do/sqlite.rb +40 -0
- data/lib/sequel/adapters/firebird.rb +283 -0
- data/lib/sequel/adapters/informix.rb +77 -0
- data/lib/sequel/adapters/jdbc.rb +587 -0
- data/lib/sequel/adapters/jdbc/as400.rb +58 -0
- data/lib/sequel/adapters/jdbc/h2.rb +133 -0
- data/lib/sequel/adapters/jdbc/mssql.rb +57 -0
- data/lib/sequel/adapters/jdbc/mysql.rb +78 -0
- data/lib/sequel/adapters/jdbc/oracle.rb +50 -0
- data/lib/sequel/adapters/jdbc/postgresql.rb +108 -0
- data/lib/sequel/adapters/jdbc/sqlite.rb +55 -0
- data/lib/sequel/adapters/mysql.rb +421 -0
- data/lib/sequel/adapters/odbc.rb +143 -0
- data/lib/sequel/adapters/odbc/mssql.rb +42 -0
- data/lib/sequel/adapters/openbase.rb +64 -0
- data/lib/sequel/adapters/oracle.rb +131 -0
- data/lib/sequel/adapters/postgres.rb +504 -0
- data/lib/sequel/adapters/shared/mssql.rb +490 -0
- data/lib/sequel/adapters/shared/mysql.rb +498 -0
- data/lib/sequel/adapters/shared/oracle.rb +195 -0
- data/lib/sequel/adapters/shared/postgres.rb +830 -0
- data/lib/sequel/adapters/shared/progress.rb +44 -0
- data/lib/sequel/adapters/shared/sqlite.rb +389 -0
- data/lib/sequel/adapters/sqlite.rb +224 -0
- data/lib/sequel/adapters/utils/stored_procedures.rb +84 -0
- data/lib/sequel/connection_pool.rb +99 -0
- data/lib/sequel/connection_pool/sharded_single.rb +84 -0
- data/lib/sequel/connection_pool/sharded_threaded.rb +211 -0
- data/lib/sequel/connection_pool/single.rb +29 -0
- data/lib/sequel/connection_pool/threaded.rb +150 -0
- data/lib/sequel/core.rb +293 -0
- data/lib/sequel/core_sql.rb +241 -0
- data/lib/sequel/database.rb +1079 -0
- data/lib/sequel/database/schema_generator.rb +327 -0
- data/lib/sequel/database/schema_methods.rb +203 -0
- data/lib/sequel/database/schema_sql.rb +320 -0
- data/lib/sequel/dataset.rb +32 -0
- data/lib/sequel/dataset/actions.rb +441 -0
- data/lib/sequel/dataset/features.rb +86 -0
- data/lib/sequel/dataset/graph.rb +254 -0
- data/lib/sequel/dataset/misc.rb +119 -0
- data/lib/sequel/dataset/mutation.rb +64 -0
- data/lib/sequel/dataset/prepared_statements.rb +227 -0
- data/lib/sequel/dataset/query.rb +709 -0
- data/lib/sequel/dataset/sql.rb +996 -0
- data/lib/sequel/exceptions.rb +51 -0
- data/lib/sequel/extensions/blank.rb +43 -0
- data/lib/sequel/extensions/inflector.rb +242 -0
- data/lib/sequel/extensions/looser_typecasting.rb +21 -0
- data/lib/sequel/extensions/migration.rb +239 -0
- data/lib/sequel/extensions/named_timezones.rb +61 -0
- data/lib/sequel/extensions/pagination.rb +100 -0
- data/lib/sequel/extensions/pretty_table.rb +82 -0
- data/lib/sequel/extensions/query.rb +52 -0
- data/lib/sequel/extensions/schema_dumper.rb +271 -0
- data/lib/sequel/extensions/sql_expr.rb +122 -0
- data/lib/sequel/extensions/string_date_time.rb +46 -0
- data/lib/sequel/extensions/thread_local_timezones.rb +48 -0
- data/lib/sequel/metaprogramming.rb +9 -0
- data/lib/sequel/model.rb +120 -0
- data/lib/sequel/model/associations.rb +1514 -0
- data/lib/sequel/model/base.rb +1069 -0
- data/lib/sequel/model/default_inflections.rb +45 -0
- data/lib/sequel/model/errors.rb +39 -0
- data/lib/sequel/model/exceptions.rb +21 -0
- data/lib/sequel/model/inflections.rb +162 -0
- data/lib/sequel/model/plugins.rb +70 -0
- data/lib/sequel/plugins/active_model.rb +59 -0
- data/lib/sequel/plugins/association_dependencies.rb +103 -0
- data/lib/sequel/plugins/association_proxies.rb +41 -0
- data/lib/sequel/plugins/boolean_readers.rb +53 -0
- data/lib/sequel/plugins/caching.rb +141 -0
- data/lib/sequel/plugins/class_table_inheritance.rb +214 -0
- data/lib/sequel/plugins/composition.rb +138 -0
- data/lib/sequel/plugins/force_encoding.rb +72 -0
- data/lib/sequel/plugins/hook_class_methods.rb +126 -0
- data/lib/sequel/plugins/identity_map.rb +116 -0
- data/lib/sequel/plugins/instance_filters.rb +98 -0
- data/lib/sequel/plugins/instance_hooks.rb +57 -0
- data/lib/sequel/plugins/lazy_attributes.rb +77 -0
- data/lib/sequel/plugins/many_through_many.rb +208 -0
- data/lib/sequel/plugins/nested_attributes.rb +206 -0
- data/lib/sequel/plugins/optimistic_locking.rb +81 -0
- data/lib/sequel/plugins/rcte_tree.rb +281 -0
- data/lib/sequel/plugins/schema.rb +66 -0
- data/lib/sequel/plugins/serialization.rb +166 -0
- data/lib/sequel/plugins/single_table_inheritance.rb +74 -0
- data/lib/sequel/plugins/subclasses.rb +45 -0
- data/lib/sequel/plugins/tactical_eager_loading.rb +61 -0
- data/lib/sequel/plugins/timestamps.rb +87 -0
- data/lib/sequel/plugins/touch.rb +118 -0
- data/lib/sequel/plugins/typecast_on_load.rb +72 -0
- data/lib/sequel/plugins/validation_class_methods.rb +405 -0
- data/lib/sequel/plugins/validation_helpers.rb +223 -0
- data/lib/sequel/sql.rb +1020 -0
- data/lib/sequel/timezones.rb +161 -0
- data/lib/sequel/version.rb +12 -0
- data/lib/sequel_core.rb +1 -0
- data/lib/sequel_model.rb +1 -0
- data/spec/adapters/firebird_spec.rb +407 -0
- data/spec/adapters/informix_spec.rb +97 -0
- data/spec/adapters/mssql_spec.rb +403 -0
- data/spec/adapters/mysql_spec.rb +1019 -0
- data/spec/adapters/oracle_spec.rb +286 -0
- data/spec/adapters/postgres_spec.rb +969 -0
- data/spec/adapters/spec_helper.rb +51 -0
- data/spec/adapters/sqlite_spec.rb +432 -0
- data/spec/core/connection_pool_spec.rb +808 -0
- data/spec/core/core_sql_spec.rb +417 -0
- data/spec/core/database_spec.rb +1662 -0
- data/spec/core/dataset_spec.rb +3827 -0
- data/spec/core/expression_filters_spec.rb +595 -0
- data/spec/core/object_graph_spec.rb +296 -0
- data/spec/core/schema_generator_spec.rb +159 -0
- data/spec/core/schema_spec.rb +830 -0
- data/spec/core/spec_helper.rb +56 -0
- data/spec/core/version_spec.rb +7 -0
- data/spec/extensions/active_model_spec.rb +76 -0
- data/spec/extensions/association_dependencies_spec.rb +127 -0
- data/spec/extensions/association_proxies_spec.rb +50 -0
- data/spec/extensions/blank_spec.rb +67 -0
- data/spec/extensions/boolean_readers_spec.rb +92 -0
- data/spec/extensions/caching_spec.rb +250 -0
- data/spec/extensions/class_table_inheritance_spec.rb +252 -0
- data/spec/extensions/composition_spec.rb +194 -0
- data/spec/extensions/force_encoding_spec.rb +117 -0
- data/spec/extensions/hook_class_methods_spec.rb +470 -0
- data/spec/extensions/identity_map_spec.rb +202 -0
- data/spec/extensions/inflector_spec.rb +181 -0
- data/spec/extensions/instance_filters_spec.rb +55 -0
- data/spec/extensions/instance_hooks_spec.rb +133 -0
- data/spec/extensions/lazy_attributes_spec.rb +153 -0
- data/spec/extensions/looser_typecasting_spec.rb +39 -0
- data/spec/extensions/many_through_many_spec.rb +884 -0
- data/spec/extensions/migration_spec.rb +332 -0
- data/spec/extensions/named_timezones_spec.rb +72 -0
- data/spec/extensions/nested_attributes_spec.rb +396 -0
- data/spec/extensions/optimistic_locking_spec.rb +100 -0
- data/spec/extensions/pagination_spec.rb +99 -0
- data/spec/extensions/pretty_table_spec.rb +91 -0
- data/spec/extensions/query_spec.rb +85 -0
- data/spec/extensions/rcte_tree_spec.rb +205 -0
- data/spec/extensions/schema_dumper_spec.rb +357 -0
- data/spec/extensions/schema_spec.rb +127 -0
- data/spec/extensions/serialization_spec.rb +209 -0
- data/spec/extensions/single_table_inheritance_spec.rb +96 -0
- data/spec/extensions/spec_helper.rb +91 -0
- data/spec/extensions/sql_expr_spec.rb +89 -0
- data/spec/extensions/string_date_time_spec.rb +93 -0
- data/spec/extensions/subclasses_spec.rb +52 -0
- data/spec/extensions/tactical_eager_loading_spec.rb +65 -0
- data/spec/extensions/thread_local_timezones_spec.rb +45 -0
- data/spec/extensions/timestamps_spec.rb +150 -0
- data/spec/extensions/touch_spec.rb +155 -0
- data/spec/extensions/typecast_on_load_spec.rb +69 -0
- data/spec/extensions/validation_class_methods_spec.rb +984 -0
- data/spec/extensions/validation_helpers_spec.rb +438 -0
- data/spec/integration/associations_test.rb +281 -0
- data/spec/integration/database_test.rb +26 -0
- data/spec/integration/dataset_test.rb +963 -0
- data/spec/integration/eager_loader_test.rb +734 -0
- data/spec/integration/model_test.rb +130 -0
- data/spec/integration/plugin_test.rb +814 -0
- data/spec/integration/prepared_statement_test.rb +213 -0
- data/spec/integration/schema_test.rb +361 -0
- data/spec/integration/spec_helper.rb +73 -0
- data/spec/integration/timezone_test.rb +55 -0
- data/spec/integration/transaction_test.rb +122 -0
- data/spec/integration/type_test.rb +96 -0
- data/spec/model/association_reflection_spec.rb +175 -0
- data/spec/model/associations_spec.rb +2633 -0
- data/spec/model/base_spec.rb +418 -0
- data/spec/model/dataset_methods_spec.rb +78 -0
- data/spec/model/eager_loading_spec.rb +1391 -0
- data/spec/model/hooks_spec.rb +240 -0
- data/spec/model/inflector_spec.rb +26 -0
- data/spec/model/model_spec.rb +593 -0
- data/spec/model/plugins_spec.rb +236 -0
- data/spec/model/record_spec.rb +1500 -0
- data/spec/model/spec_helper.rb +97 -0
- data/spec/model/validations_spec.rb +153 -0
- data/spec/rcov.opts +6 -0
- data/spec/spec_config.rb.example +10 -0
- 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
|