dohmysql 0.1.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/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
|