dohmysql 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +20 -0
- data/bin/makedb +28 -0
- data/lib/doh/mysql/abstract_row.rb +80 -0
- data/lib/doh/mysql/activate.rb +31 -0
- data/lib/doh/mysql/cache_connector.rb +54 -0
- data/lib/doh/mysql/connector_instance.rb +79 -0
- data/lib/doh/mysql/connector_util.rb +27 -0
- data/lib/doh/mysql/convert.rb +18 -0
- data/lib/doh/mysql/current_date.rb +22 -0
- data/lib/doh/mysql/database_creator.rb +101 -0
- data/lib/doh/mysql/db_date.rb +28 -0
- data/lib/doh/mysql/default_type_guesser.rb +37 -0
- data/lib/doh/mysql/error.rb +7 -0
- data/lib/doh/mysql/handle.rb +218 -0
- data/lib/doh/mysql/hash_row.rb +13 -0
- data/lib/doh/mysql/load_sql.rb +26 -0
- data/lib/doh/mysql/metadata_util.rb +73 -0
- data/lib/doh/mysql/parse.rb +36 -0
- data/lib/doh/mysql/raw_row_builder.rb +15 -0
- data/lib/doh/mysql/readonly_row.rb +26 -0
- data/lib/doh/mysql/require_dbtypes.rb +8 -0
- data/lib/doh/mysql/smart_row.rb +156 -0
- data/lib/doh/mysql/to_sql.rb +65 -0
- data/lib/doh/mysql/typed_row_builder.rb +28 -0
- data/lib/doh/mysql/types.rb +33 -0
- data/lib/doh/mysql/unquoted.rb +17 -0
- data/lib/doh/mysql/version.rb +102 -0
- data/lib/doh/mysql/virtual.rb +17 -0
- data/lib/doh/mysql/writable_row.rb +59 -0
- data/lib/doh/mysql.rb +7 -0
- data/test/cache_connector.dt.rb +41 -0
- data/test/connector.yml +4 -0
- data/test/connector.yml.tmpl +4 -0
- data/test/connector_instance.dt.rb +32 -0
- data/test/convert.dt.rb +45 -0
- data/test/db_unit_test.rb +10 -0
- data/test/handle.dt.rb +112 -0
- data/test/metadata_util.dt.rb +53 -0
- data/test/parse.dt.rb +39 -0
- data/test/readonly_row.dt.rb +85 -0
- data/test/smart_row.dt.rb +21 -0
- data/test/to_sql.dt.rb +19 -0
- data/test/types.dt.rb +32 -0
- data/test/unquoted.dt.rb +16 -0
- data/test/writable_row.dt.rb +21 -0
- metadata +118 -0
@@ -0,0 +1,218 @@
|
|
1
|
+
require 'doh/array_to_hash'
|
2
|
+
require 'doh/log/stub'
|
3
|
+
require 'doh/mysql/error'
|
4
|
+
require 'doh/mysql/typed_row_builder'
|
5
|
+
require 'doh/mysql/writable_row'
|
6
|
+
require 'doh/mysql/hash_row'
|
7
|
+
require 'doh/mysql/smart_row'
|
8
|
+
|
9
|
+
module DohDb
|
10
|
+
|
11
|
+
class Handle
|
12
|
+
def initialize(mysqlh, row_builder = nil)
|
13
|
+
@mysqlh = mysqlh
|
14
|
+
@row_builder = row_builder || TypedRowBuilder.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def close
|
18
|
+
unless closed?
|
19
|
+
dohlog.info("closing raw mysql handle: #@mysqlh")
|
20
|
+
@mysqlh.close
|
21
|
+
@mysqlh = nil
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def closed?
|
26
|
+
@mysqlh.nil?
|
27
|
+
end
|
28
|
+
|
29
|
+
def query(statement)
|
30
|
+
generic_query(statement)
|
31
|
+
retval = @mysqlh.affected_rows
|
32
|
+
dohlog.info("affected #{retval} rows")
|
33
|
+
retval
|
34
|
+
end
|
35
|
+
|
36
|
+
def update(statement)
|
37
|
+
generic_query(statement)
|
38
|
+
retval = @mysqlh.affected_rows
|
39
|
+
dohlog.info("updated #{retval} rows")
|
40
|
+
retval
|
41
|
+
end
|
42
|
+
|
43
|
+
def update_row(statement)
|
44
|
+
retval = update(statement)
|
45
|
+
raise UnexpectedQueryResult, "updated #{retval} rows; expected 1" unless retval == 1
|
46
|
+
retval
|
47
|
+
end
|
48
|
+
|
49
|
+
def update_hash(hash, table, primary_key_value, primary_key_name)
|
50
|
+
items = hash.keys.collect {|key| key + ' = ' + hash[key].to_sql}
|
51
|
+
query("UPDATE #{table} SET #{items.join(', ')} WHERE #{primary_key_name} = #{primary_key_value.to_sql}")
|
52
|
+
end
|
53
|
+
|
54
|
+
def insert(statement)
|
55
|
+
generic_query(statement)
|
56
|
+
retval = @mysqlh.insert_id
|
57
|
+
dohlog.info("insert_id was #{retval}")
|
58
|
+
retval
|
59
|
+
end
|
60
|
+
|
61
|
+
def insert_hash(hash, table, ignore = nil)
|
62
|
+
if ignore then keyword = 'INSERT IGNORE' else keyword = 'INSERT' end
|
63
|
+
insert_hash_helper(hash, table, keyword)
|
64
|
+
end
|
65
|
+
|
66
|
+
def replace_hash(hash, table)
|
67
|
+
insert_hash_helper(hash, table, 'REPLACE')
|
68
|
+
end
|
69
|
+
|
70
|
+
# The most generic form of select.
|
71
|
+
# It calls to_s on the statement object to facilitate the use of sql builder objects.
|
72
|
+
def select(statement, row_builder = nil)
|
73
|
+
result_set = generic_query(statement)
|
74
|
+
dohlog.info("selected #{result_set.num_rows} rows")
|
75
|
+
rows = get_row_builder(row_builder).build_rows(result_set)
|
76
|
+
result_set.free
|
77
|
+
rows
|
78
|
+
end
|
79
|
+
|
80
|
+
# Simple convenience wrapper around the generic select call.
|
81
|
+
# Throws an exception unless the result set is a single row.
|
82
|
+
# Returns the row selected.
|
83
|
+
def select_row(statement, row_builder = nil)
|
84
|
+
rows = select(statement, row_builder)
|
85
|
+
raise UnexpectedQueryResult, "selected #{rows.size} rows; expected 1" unless rows.size == 1
|
86
|
+
rows[0]
|
87
|
+
end
|
88
|
+
|
89
|
+
# Simple convenience wrapper around the generic select call.
|
90
|
+
# Throws an exception unless the result set is empty or a single row.
|
91
|
+
# Returns nil if the result set is empty, or the row selected.
|
92
|
+
def select_optional_row(statement, row_builder = nil)
|
93
|
+
rows = select(statement, row_builder)
|
94
|
+
raise UnexpectedQueryResult, "selected #{rows.size} rows; expected 0 or 1" if rows.size > 1
|
95
|
+
if rows.empty? then nil else rows[0] end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Simple convenience wrapper around select_row.
|
99
|
+
# Returns the first (and typically, the only) field from the selected row.
|
100
|
+
def select_field(statement, row_builder = nil)
|
101
|
+
select_row(statement, row_builder).at(0)
|
102
|
+
end
|
103
|
+
|
104
|
+
# Simple convenience wrapper around select_optional_row.
|
105
|
+
# Returns the first (and typically, the only) field from the selected row, if any, or nil.
|
106
|
+
def select_optional_field(statement, row_builder = nil)
|
107
|
+
row = select_optional_row(statement, row_builder)
|
108
|
+
row && row.at(0)
|
109
|
+
end
|
110
|
+
|
111
|
+
# Rows in the result set must have 2 or more fields.
|
112
|
+
# If there are 2 fields, returns a hash where each key is the first field in the result set, and the value is the second field.
|
113
|
+
# If there are more than 2 fields, returns a hash where each key is the first field in the result set,
|
114
|
+
# and the value is the row itself, as a Hash, and without the field used as a key.
|
115
|
+
def select_transpose(statement, row_builder = nil)
|
116
|
+
rows = select(statement, row_builder)
|
117
|
+
return {} if rows.empty?
|
118
|
+
field_count = rows.first.size
|
119
|
+
if field_count < 2
|
120
|
+
raise UnexpectedQueryResult, "must select at least 2 fields in order to transpose"
|
121
|
+
elsif field_count == 2
|
122
|
+
Doh::array_to_hash(rows) { |row| [row.at(0), row.at(1)] }
|
123
|
+
else
|
124
|
+
key_field = rows.first.keys.first
|
125
|
+
Doh::array_to_hash(rows) do |row|
|
126
|
+
value = row.to_h
|
127
|
+
value.delete(key_field)
|
128
|
+
[row.at(0), value]
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# Returns an array of arrays, where the individual arrays contain just the values from each database row -- they lack field names.
|
134
|
+
def select_values(statement, row_builder = nil)
|
135
|
+
select(statement, row_builder).collect { |row| row.values }
|
136
|
+
end
|
137
|
+
|
138
|
+
# Returns an array of the first (and typically, the only) field of every row in the result set.
|
139
|
+
def select_list(statement, row_builder = nil)
|
140
|
+
select(statement, row_builder).collect { |row| row.at(0) }
|
141
|
+
end
|
142
|
+
|
143
|
+
def multi_select(statement_infos, dflt_row_builder = nil)
|
144
|
+
statements = []
|
145
|
+
row_builders = []
|
146
|
+
statement_infos.each do |info|
|
147
|
+
if info.is_a?(Array)
|
148
|
+
statements.push(info.first)
|
149
|
+
row_builders.push(info.last)
|
150
|
+
else
|
151
|
+
statements.push(info)
|
152
|
+
row_builders.push(nil)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
sqlstr = statements.join(';')
|
156
|
+
|
157
|
+
dohlog.info(sqlstr)
|
158
|
+
@mysqlh.query(sqlstr)
|
159
|
+
retval = []
|
160
|
+
while true
|
161
|
+
custom_builder = row_builders.shift
|
162
|
+
if @mysqlh.field_count > 0
|
163
|
+
result_set = @mysqlh.store_result
|
164
|
+
dohlog.info("selected #{result_set.num_rows} rows")
|
165
|
+
rows = get_row_builder(custom_builder || dflt_row_builder).build_rows(result_set)
|
166
|
+
result_set.free
|
167
|
+
retval.push(rows)
|
168
|
+
end
|
169
|
+
break unless @mysqlh.next_result
|
170
|
+
end
|
171
|
+
retval
|
172
|
+
rescue Exception => excpt
|
173
|
+
dohlog.error("caught exception during query: #{sqlstr}", excpt)
|
174
|
+
raise
|
175
|
+
end
|
176
|
+
|
177
|
+
private
|
178
|
+
def generic_query(statement)
|
179
|
+
sqlstr = statement.to_s
|
180
|
+
dohlog.info(sqlstr)
|
181
|
+
@mysqlh.query(sqlstr)
|
182
|
+
@mysqlh.store_result if @mysqlh.field_count > 0
|
183
|
+
rescue Exception => excpt
|
184
|
+
dohlog.error("caught exception during query: #{sqlstr}", excpt)
|
185
|
+
raise
|
186
|
+
end
|
187
|
+
|
188
|
+
def insert_hash_helper(hash, table, keyword)
|
189
|
+
names = []
|
190
|
+
values = []
|
191
|
+
hash.each_pair do |key, value|
|
192
|
+
names.push(key)
|
193
|
+
values.push(value.to_sql)
|
194
|
+
end
|
195
|
+
|
196
|
+
insert("#{keyword} INTO #{table} (#{names.join(',')}) VALUES (#{values.join(',')})")
|
197
|
+
end
|
198
|
+
|
199
|
+
def get_row_builder(row_builder = nil)
|
200
|
+
if row_builder.nil?
|
201
|
+
@row_builder
|
202
|
+
elsif row_builder == :read
|
203
|
+
TypedRowBuilder.new(ReadOnlyRow)
|
204
|
+
elsif row_builder == :hash
|
205
|
+
TypedRowBuilder.new(HashRow)
|
206
|
+
elsif row_builder == :write
|
207
|
+
TypedRowBuilder.new(WritableRow)
|
208
|
+
elsif row_builder == :smart
|
209
|
+
TypedRowBuilder.new(SmartRow)
|
210
|
+
elsif row_builder.respond_to?('build_rows')
|
211
|
+
row_builder
|
212
|
+
else
|
213
|
+
TypedRowBuilder.new(row_builder)
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'doh/mysql/connector_instance'
|
2
|
+
|
3
|
+
module DohDb
|
4
|
+
|
5
|
+
def self.mysql_arg(value, option_specifier)
|
6
|
+
return '' if value.to_s.strip.empty?
|
7
|
+
' -' + option_specifier + value
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.load_sql(filenames, host, username, password, database)
|
11
|
+
mysqlcmd = 'mysql' + mysql_arg(host, 'h') + mysql_arg(username, 'u') + mysql_arg(password, 'p') + ' ' + database
|
12
|
+
io = IO::popen(mysqlcmd, 'r+')
|
13
|
+
dohlog.debug("loading sql file: " + filenames.first) if filenames.size == 1
|
14
|
+
filenames.each do |elem|
|
15
|
+
open(elem) {|file| io << file.read}
|
16
|
+
end
|
17
|
+
io.close
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.load_sql_connector(filenames, connector = nil, alternate_database = nil)
|
21
|
+
connector ||= DohDb::connector_instance
|
22
|
+
load_sql(filenames, connector.host, connector.username, connector.password, alternate_database || connector.database)
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'doh/mysql'
|
2
|
+
require 'doh/mysql/to_sql'
|
3
|
+
|
4
|
+
module DohDb
|
5
|
+
|
6
|
+
@@cached_column_info = {}
|
7
|
+
def self.column_info(table, database = nil)
|
8
|
+
database ||= DohDb::connector_instance.database
|
9
|
+
lookup_str = database + '.' + table
|
10
|
+
return @@cached_column_info[lookup_str] if @@cached_column_info[lookup_str]
|
11
|
+
stmt = "SELECT column_name, is_nullable, data_type, character_maximum_length, numeric_scale, column_type FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=#{database.to_sql} AND TABLE_NAME=#{table.to_sql}"
|
12
|
+
@@cached_column_info[lookup_str] = DohDb::select_transpose(stmt)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.field_character_size(table, field, database = nil)
|
16
|
+
column_info(table, database).fetch(field, {}).fetch('character_maximum_length')
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.chop_character_fields!(table, row)
|
20
|
+
column_info(table).each do |field, attribs|
|
21
|
+
maxlen = attribs['character_maximum_length']
|
22
|
+
if maxlen && row[field].to_s.size > maxlen
|
23
|
+
row[field] = row[field].to_s[0, maxlen]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
row
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.chop_character_fields(table, row)
|
30
|
+
chop_character_fields!(table, row.dup)
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.field_exist?(table, field, database = nil)
|
34
|
+
column_info(table, database).key?(field)
|
35
|
+
end
|
36
|
+
|
37
|
+
@@primary_keys = {}
|
38
|
+
def self.find_primary_key(table, database = nil)
|
39
|
+
if table.index('.')
|
40
|
+
database = table.before('.')
|
41
|
+
table = table.after('.')
|
42
|
+
else
|
43
|
+
database ||= DohDb::connector_instance.database
|
44
|
+
end
|
45
|
+
|
46
|
+
dbhash = @@primary_keys[database]
|
47
|
+
if dbhash.nil?
|
48
|
+
dbhash = DohDb::select_transpose("SELECT table_name, column_name FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=#{database.to_sql} AND ORDINAL_POSITION=1")
|
49
|
+
raise "no information found for database #{database}" if dbhash.empty?
|
50
|
+
@@primary_keys[database] = dbhash
|
51
|
+
end
|
52
|
+
|
53
|
+
retval = dbhash[table]
|
54
|
+
raise "attempting to find_primary_key for table that doesn't exist: #{database}.#{table}" if retval.nil?
|
55
|
+
retval
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.table_exist?(table, database = nil)
|
59
|
+
!find_primary_key(table, database).nil?
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.field_list(table, database = nil)
|
63
|
+
column_info(table, database).keys
|
64
|
+
end
|
65
|
+
|
66
|
+
@@tables_by_database = {}
|
67
|
+
def self.all_tables(database = nil)
|
68
|
+
database ||= DohDb::connector_instance.database
|
69
|
+
@@tables_by_database[database] ||
|
70
|
+
@@tables_by_database[database] ||= DohDb::select_list("SELECT table_name FROM information_schema.tables WHERE table_schema = '#{database}'")
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'bigdecimal'
|
2
|
+
require 'date'
|
3
|
+
|
4
|
+
module DohDb
|
5
|
+
|
6
|
+
def self.parse_bool(str)
|
7
|
+
if str == '0'
|
8
|
+
false
|
9
|
+
elsif str == '1'
|
10
|
+
true
|
11
|
+
else
|
12
|
+
raise ArgumentError.new("unexpected value: " + str)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.parse_datetime(str)
|
17
|
+
raise ArgumentError.new("unexpected value: " + str) unless str.size == 19
|
18
|
+
return nil if str == '0000-00-00 00:00:00'
|
19
|
+
DateTime.parse(str)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.parse_date(str)
|
23
|
+
raise ArgumentError.new("unexpected value: " + str) unless str.size == 10
|
24
|
+
return nil if str == '0000-00-00'
|
25
|
+
Date.new(str[0..3].to_i, str[5..6].to_i, str[8..9].to_i)
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.parse_decimal(str)
|
29
|
+
BigDecimal(str)
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.parse_int(str)
|
33
|
+
str.to_i
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'mysql'
|
2
|
+
require 'doh/mysql/readonly_row'
|
3
|
+
|
4
|
+
module DohDb
|
5
|
+
|
6
|
+
class RawRowBuilder
|
7
|
+
def build_rows(result_set)
|
8
|
+
field_names = result_set.fetch_fields.collect {|elem| elem.name}
|
9
|
+
retval = []
|
10
|
+
result_set.each {|elem| retval.push(DohDb::ReadOnlyRow.new(field_names, elem))}
|
11
|
+
retval
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'doh/mysql/abstract_row'
|
2
|
+
|
3
|
+
module DohDb
|
4
|
+
|
5
|
+
class ReadOnlyRow < AbstractRow
|
6
|
+
# can accept 2 arguments: keys array, values array
|
7
|
+
# or 1 argument: a hash
|
8
|
+
def initialize(*args)
|
9
|
+
keys, values = parse_initialize_args(*args)
|
10
|
+
@keys = keys.freeze
|
11
|
+
@values = values.freeze
|
12
|
+
freeze
|
13
|
+
end
|
14
|
+
|
15
|
+
def method_missing(sym, *ignore)
|
16
|
+
key = sym.to_s
|
17
|
+
index = @keys.index(key)
|
18
|
+
if index
|
19
|
+
@values.at(index)
|
20
|
+
else
|
21
|
+
raise RuntimeError.new("unknown field: " + key)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,156 @@
|
|
1
|
+
require 'doh/mysql/writable_row'
|
2
|
+
require 'doh/to_display'
|
3
|
+
require 'doh/mysql/virtual'
|
4
|
+
|
5
|
+
module DohDb
|
6
|
+
|
7
|
+
class RowDisplayProxy
|
8
|
+
def initialize(row)
|
9
|
+
@row = row
|
10
|
+
end
|
11
|
+
|
12
|
+
def method_missing(sym, *ignore_args)
|
13
|
+
@row.send(sym.to_s).to_display
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class AbstractSmartRow < AbstractRow
|
18
|
+
attr_accessor :table
|
19
|
+
attr_reader :changed_keys
|
20
|
+
|
21
|
+
def initialize(keys, values)
|
22
|
+
@keys = keys.dup
|
23
|
+
@values = values.dup
|
24
|
+
@changed_keys = Set.new
|
25
|
+
@cached_virtuals = {}
|
26
|
+
end
|
27
|
+
|
28
|
+
def initialize_copy(orig)
|
29
|
+
@keys = @keys.dup
|
30
|
+
@values = @values.dup
|
31
|
+
@changed_keys = @changed_keys.dup
|
32
|
+
@cached_virtuals = @cached_virtuals.dup
|
33
|
+
@display_proxy = nil
|
34
|
+
end
|
35
|
+
|
36
|
+
def display(key = nil)
|
37
|
+
if key.nil?
|
38
|
+
@display_proxy ||= RowDisplayProxy.new(self)
|
39
|
+
else
|
40
|
+
get(key).to_display
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def set(key, value, flag_changed = true)
|
45
|
+
index = @keys.index(key)
|
46
|
+
if index
|
47
|
+
if @values[index] != value
|
48
|
+
@values[index] = value
|
49
|
+
@changed_keys.add(key)
|
50
|
+
end
|
51
|
+
else
|
52
|
+
@keys.push(key)
|
53
|
+
@values.push(value)
|
54
|
+
@changed_keys.add(key)
|
55
|
+
end
|
56
|
+
value
|
57
|
+
end
|
58
|
+
alias []= set
|
59
|
+
|
60
|
+
def clear_changed_keys
|
61
|
+
@changed_keys.clear
|
62
|
+
end
|
63
|
+
|
64
|
+
def method_missing(sym, *args)
|
65
|
+
name = sym.to_s
|
66
|
+
if name.end_with?('=')
|
67
|
+
guess_missing_set(name[0..-2], args.first)
|
68
|
+
else
|
69
|
+
guess_missing_get(name)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def merge!(hash)
|
74
|
+
hash.each_pair { |key, value| set(key, value) }
|
75
|
+
end
|
76
|
+
|
77
|
+
def delete(key)
|
78
|
+
index = @keys.index(key)
|
79
|
+
return unless index
|
80
|
+
@keys.delete_at(index)
|
81
|
+
@values.delete_at(index)
|
82
|
+
end
|
83
|
+
|
84
|
+
def record_id=(value)
|
85
|
+
set(primary_key, value)
|
86
|
+
end
|
87
|
+
|
88
|
+
def db_insert
|
89
|
+
newid = DohDb::insert_hash(self, @table)
|
90
|
+
if newid != 0
|
91
|
+
set(primary_key, newid, false)
|
92
|
+
end
|
93
|
+
newid
|
94
|
+
end
|
95
|
+
|
96
|
+
def db_update
|
97
|
+
return if @changed_keys.empty?
|
98
|
+
before_db_update
|
99
|
+
builder = BuildSQL::SimpleUpdateRow.new(@table, nil, get(primary_key), primary_key)
|
100
|
+
@changed_keys.each {|key| builder.setc(key, get(key).to_sql)}
|
101
|
+
DohDb::query(builder)
|
102
|
+
after_db_update
|
103
|
+
@changed_keys.clear
|
104
|
+
end
|
105
|
+
|
106
|
+
protected
|
107
|
+
def primary_key
|
108
|
+
DohDb::find_primary_key(@table)
|
109
|
+
end
|
110
|
+
|
111
|
+
def before_db_update
|
112
|
+
end
|
113
|
+
|
114
|
+
def after_db_update
|
115
|
+
end
|
116
|
+
|
117
|
+
def guess_missing_get(key)
|
118
|
+
return get(key) if key?(key)
|
119
|
+
retval = get_virtual_field(key)
|
120
|
+
raise "unknown field: #{key}" unless retval
|
121
|
+
retval
|
122
|
+
end
|
123
|
+
|
124
|
+
def guess_missing_set(key, value)
|
125
|
+
set(key, value)
|
126
|
+
end
|
127
|
+
|
128
|
+
def get_virtual_field(name)
|
129
|
+
cached = @cached_virtuals[name]
|
130
|
+
return cached if cached
|
131
|
+
if idvalue = get(name + '_id')
|
132
|
+
@cached_virtuals[name] = DohDb::LinkedRow.build(name, idvalue.to_i)
|
133
|
+
else
|
134
|
+
nil
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
class SmartRow < AbstractSmartRow
|
140
|
+
def initialize(*args)
|
141
|
+
parsed_args = parse_initialize_args(*args)
|
142
|
+
if parsed_args.size == 3
|
143
|
+
@table = parsed_args.pop
|
144
|
+
end
|
145
|
+
super(*parsed_args)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
class CustomSmartRow < AbstractSmartRow
|
150
|
+
def initialize(*args)
|
151
|
+
super(*parse_initialize_args(*args))
|
152
|
+
@table = self.class.default_table
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'bigdecimal'
|
2
|
+
require 'date'
|
3
|
+
begin
|
4
|
+
require 'mysql'
|
5
|
+
$mysql_loaded = true
|
6
|
+
rescue
|
7
|
+
$mysql_loaded = false
|
8
|
+
end
|
9
|
+
|
10
|
+
class Object
|
11
|
+
def to_sql
|
12
|
+
if $mysql_loaded
|
13
|
+
str = Mysql.escape_string(to_s)
|
14
|
+
else
|
15
|
+
str = non_mysql_escape_string
|
16
|
+
end
|
17
|
+
'"' + str + '"'
|
18
|
+
end
|
19
|
+
private
|
20
|
+
def non_mysql_escape_string
|
21
|
+
str.gsub('\\', '\\\\').gsub('\'', '\\\'').gsub('"', '\\"')
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class NilClass
|
26
|
+
def to_sql
|
27
|
+
'NULL'
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class Numeric
|
32
|
+
def to_sql
|
33
|
+
to_s
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class DateTime
|
38
|
+
def to_sql
|
39
|
+
'"' + strftime('%Y-%m-%d %H:%M:%S') + '"'
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class TrueClass
|
44
|
+
def to_sql
|
45
|
+
'1'
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class FalseClass
|
50
|
+
def to_sql
|
51
|
+
'0'
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class BigDecimal
|
56
|
+
def to_sql
|
57
|
+
to_s('F')
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class Array
|
62
|
+
def to_sql
|
63
|
+
'(' + collect { |elem| elem.to_sql }.join(',') + ')'
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'doh/mysql/readonly_row'
|
2
|
+
require 'doh/mysql/default_type_guesser'
|
3
|
+
|
4
|
+
module DohDb
|
5
|
+
|
6
|
+
class TypedRowBuilder
|
7
|
+
def initialize(row_klass = nil, guesser = nil)
|
8
|
+
@row_klass = row_klass || ReadOnlyRow
|
9
|
+
@guesser = guesser || DefaultTypeGuesser
|
10
|
+
end
|
11
|
+
|
12
|
+
def build_rows(result_set)
|
13
|
+
meta_info = result_set.fetch_fields
|
14
|
+
field_names = meta_info.collect {|elem| elem.name}
|
15
|
+
|
16
|
+
retval = []
|
17
|
+
result_set.each do |row|
|
18
|
+
typed_values = []
|
19
|
+
row.each_with_index do |field, index|
|
20
|
+
typed_values[index] = @guesser.guess_type(field, meta_info[index])
|
21
|
+
end
|
22
|
+
retval.push(@row_klass.new(field_names, typed_values))
|
23
|
+
end
|
24
|
+
retval
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module DohDb
|
2
|
+
|
3
|
+
@@column_types = {}
|
4
|
+
def self.register_column_type(database, table, column, klass)
|
5
|
+
database = '__global__' # until database support is available
|
6
|
+
@@column_types[database] ||= {}
|
7
|
+
table = '__global__' if table.nil?
|
8
|
+
@@column_types[database][table] ||= {}
|
9
|
+
@@column_types[database][table][column] = klass
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.find_column_type(database, table, column)
|
13
|
+
database = '__global__' # until database support is available
|
14
|
+
db_hash = @@column_types[database]; return nil unless db_hash
|
15
|
+
table = '__global__' if table.nil?
|
16
|
+
table_hash = db_hash[table]; return nil unless table_hash
|
17
|
+
table_hash[column]
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.link_database_types(dest_db, source_db)
|
21
|
+
@@column_types[dest_db] = @@column_types[source_db]
|
22
|
+
end
|
23
|
+
|
24
|
+
@@row_types = {}
|
25
|
+
def self.register_row_type(table, klass)
|
26
|
+
@@row_types[table] = klass
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.find_row_type(table)
|
30
|
+
@@row_types[table]
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|