baza 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,87 @@
1
+ #This class buffers a lot of queries and flushes them out via transactions.
2
+ class Baza::QueryBuffer
3
+ #Constructor. Takes arguments to be used and a block.
4
+ def initialize(args)
5
+ @args = args
6
+ @queries = []
7
+ @inserts = {}
8
+ @queries_count = 0
9
+ @debug = @args[:debug]
10
+ @lock = Mutex.new
11
+
12
+ STDOUT.puts "Query buffer started." if @debug
13
+
14
+ if block_given?
15
+ begin
16
+ yield(self)
17
+ ensure
18
+ self.flush
19
+ end
20
+ end
21
+ end
22
+
23
+ #Adds a query to the buffer.
24
+ def query(str)
25
+ @lock.synchronize do
26
+ STDOUT.print "Adding to buffer: #{str}\n" if @debug
27
+ @queries << str
28
+ @queries_count += 1
29
+ end
30
+
31
+ self.flush if @queries_count >= 1000
32
+ return nil
33
+ end
34
+
35
+ #Delete as on a normal Baza::Db.
36
+ #===Examples
37
+ # db.q_buffer do |buffer|
38
+ # buffer.delete(:users, {:id => 5})
39
+ # end
40
+ def delete(table, where)
41
+ STDOUT.puts "Delete called on table #{table} with arguments: '#{where}'." if @debug
42
+ self.query(@args[:db].delete(table, where, :return_sql => true))
43
+ return nil
44
+ end
45
+
46
+ #Plans to inset a hash into a table. It will only be inserted when flush is called.
47
+ #===Examples
48
+ # db.q_buffer do |buffer|
49
+ # buffer.insert(:users, {:name => "John Doe"})
50
+ # end
51
+ def insert(table, data)
52
+ @lock.synchronize do
53
+ @inserts[table] = [] if !@inserts.key?(table)
54
+ @inserts[table] << data
55
+ @queries_count += 1
56
+ end
57
+
58
+ self.flush if @queries_count >= 1000
59
+ return nil
60
+ end
61
+
62
+ #Flushes all queries out in a transaction. This will automatically be called for every 1000 queries.
63
+ def flush
64
+ return nil if @queries_count <= 0
65
+
66
+ @lock.synchronize do
67
+ @args[:db].transaction do
68
+ @queries.shift(1000).each do |str|
69
+ STDOUT.print "Executing via buffer: #{str}\n" if @debug
70
+ @args[:db].q(str)
71
+ end
72
+
73
+ @inserts.each do |table, datas_arr|
74
+ while !datas_arr.empty?
75
+ datas_chunk_arr = datas_arr.shift(1000)
76
+ @args[:db].insert_multi(table, datas_chunk_arr)
77
+ end
78
+ end
79
+ end
80
+
81
+ @inserts.clear
82
+ @queries_count = 0
83
+ end
84
+
85
+ return nil
86
+ end
87
+ end
@@ -0,0 +1,342 @@
1
+ #This class takes a database-schema from a hash and runs it against the database. It then checks that the database matches the given schema.
2
+ #
3
+ #===Examples
4
+ # db = Baza::Db.new(:type => "sqlite3", :path => "test_db.sqlite3")
5
+ # schema = {
6
+ # "tables" => {
7
+ # "User" => {
8
+ # "columns" => [
9
+ # {"name" => "id", "type" => "int", "autoincr" => true, "primarykey" => true},
10
+ # {"name" => "name", "type" => "varchar"},
11
+ # {"name" => "lastname", "type" => "varchar"}
12
+ # ],
13
+ # "indexes" => [
14
+ # "name",
15
+ # {"name" => "lastname", "columns" => ["lastname"]}
16
+ # ],
17
+ # "on_create_after" => proc{|d|
18
+ # d["db"].insert("User", {"name" => "John", "lastname" => "Doe"})
19
+ # }
20
+ # }
21
+ # }
22
+ # }
23
+ #
24
+ # rev = Baza::Revision.new
25
+ # rev.init_db("db" => db, "schema" => schema)
26
+ class Baza::Revision
27
+ def initialize(args = {})
28
+ @args = args
29
+ end
30
+
31
+ #This initializes a database-structure and content based on a schema-hash.
32
+ #===Examples
33
+ # dbrev = Baza::Revision.new
34
+ # dbrev.init_db("db" => db_obj, "schema" => schema_hash)
35
+ def init_db(args)
36
+ schema = args["schema"]
37
+ db = args["db"]
38
+
39
+ #Check for normal bugs and raise apropiate error.
40
+ raise "'schema' argument was not a Hash: '#{schema.class.name}'." if !schema.is_a?(Hash)
41
+ raise "':return_keys' is not 'symbols' - Knjdbrevision will not work without it." if db.opts[:return_keys] != "symbols"
42
+ raise "No tables given." if !schema.has_key?("tables")
43
+
44
+ #Cache tables to avoid constant reloading.
45
+ if !args.key?("tables_cache") or args["tables_cache"]
46
+ print "Caching tables-list.\n" if args["debug"]
47
+ tables = db.tables.list
48
+ else
49
+ print "Skipping tables-cache.\n" if args["debug"]
50
+ end
51
+
52
+ schema["tables"].each do |table_name, table_data|
53
+ begin
54
+ begin
55
+ table_obj = db.tables[table_name]
56
+
57
+ #Cache indexes- and column-objects to avoid constant reloading.
58
+ cols = table_obj.columns
59
+ indexes = table_obj.indexes
60
+
61
+ if table_data["columns"]
62
+ first_col = true
63
+ table_data["columns"].each do |col_data|
64
+ begin
65
+ col_obj = table_obj.column(col_data["name"])
66
+ col_str = "#{table_name}.#{col_obj.name}"
67
+ type = col_data["type"].to_s
68
+ dochange = false
69
+
70
+ if !first_col and !col_data["after"]
71
+ #Try to find out the previous column - if so we can set "after" which makes the column being created in the right order as defined.
72
+ if !col_data.has_key?("after")
73
+ prev_no = table_data["columns"].index(col_data)
74
+ if prev_no != nil and prev_no != 0
75
+ prev_no = prev_no - 1
76
+ prev_col_data = table_data["columns"][prev_no]
77
+ col_data["after"] = prev_col_data["name"]
78
+ end
79
+ end
80
+
81
+ actual_after = nil
82
+ set_next = false
83
+ cols.each do |col_name, col_iter|
84
+ if col_iter.name == col_obj.name
85
+ break
86
+ else
87
+ actual_after = col_iter.name
88
+ end
89
+ end
90
+
91
+ if actual_after != col_data["after"]
92
+ print "Changing '#{col_str}' after from '#{actual_after}' to '#{col_data["after"]}'.\n" if args["debug"]
93
+ dochange = true
94
+ end
95
+ end
96
+
97
+ #BUGFIX: When using SQLite3 the primary-column or a autoincr-column may never change type from int... This will break it!
98
+ if db.opts[:type] == "sqlite3" and col_obj.type.to_s == "int" and (col_data["primarykey"] or col_data["autoincr"]) and db.int_types.index(col_data["type"].to_s)
99
+ type = "int"
100
+ end
101
+
102
+ if type and col_obj.type.to_s != type
103
+ print "Type mismatch on #{col_str}: #{col_data["type"]}, #{col_obj.type}\n" if args["debug"]
104
+ dochange = true
105
+ end
106
+
107
+ if col_data.has_key?("primarykey") and col_obj.primarykey? != col_data["primarykey"]
108
+ print "Primary-key mismatch for #{col_str}: #{col_data["primarykey"]}, #{col_obj.primarykey?}\n" if args["debug"]
109
+ dochange = true
110
+ end
111
+
112
+ if col_data.has_key?("autoincr") and col_obj.autoincr? != col_data["autoincr"]
113
+ print "Auto-increment mismatch for #{col_str}: #{col_data["autoincr"]}, #{col_obj.autoincr?}\n" if args["debug"]
114
+ dochange = true
115
+ end
116
+
117
+ if col_data.has_key?("maxlength") and col_obj.maxlength.to_s != col_data["maxlength"].to_s
118
+ print "Maxlength mismatch on #{col_str}: #{col_data["maxlength"]}, #{col_obj.maxlength}\n" if args["debug"]
119
+ dochange = true
120
+ end
121
+
122
+ if col_data.has_key?("null") and col_obj.null?.to_s != col_data["null"].to_s
123
+ print "Null mismatch on #{col_str}: #{col_data["null"]}, #{col_obj.null?}\n" if args["debug"]
124
+ dochange = true
125
+ end
126
+
127
+ if col_data.has_key?("default") and col_obj.default.to_s != col_data["default"].to_s
128
+ print "Default mismatch on #{col_str}: #{col_data["default"]}, #{col_obj.default}\n" if args["debug"]
129
+ dochange = true
130
+ end
131
+
132
+ if col_data.has_key?("comment") and col_obj.respond_to?(:comment) and col_obj.comment.to_s != col_data["comment"].to_s
133
+ print "Comment mismatch on #{col_str}: #{col_data["comment"]}, #{col_obj.comment}\n" if args["debug"]
134
+ dochange = true
135
+ end
136
+
137
+ if col_data.is_a?(Hash) and col_data["on_before_alter"]
138
+ callback_data = col_data["on_before_alter"].call("db" => db, "table" => table_obj, "col" => col_obj, "col_data" => col_data)
139
+ if callback_data and callback_data["action"]
140
+ if callback_data["action"] == "retry"
141
+ raise Knj::Errors::Retry
142
+ end
143
+ end
144
+ end
145
+
146
+ if dochange
147
+ col_obj.change(col_data)
148
+
149
+ #Change has been made - update cache.
150
+ cols = table_obj.columns
151
+ end
152
+
153
+ first_col = false
154
+ rescue Errno::ENOENT => e
155
+ print "Column not found: #{table_obj.name}.#{col_data["name"]}.\n" if args["debug"]
156
+
157
+ if col_data.has_key?("renames")
158
+ raise "'renames' was not an array for column '#{table_obj.name}.#{col_data["name"]}'." if !col_data["renames"].is_a?(Array)
159
+
160
+ rename_found = false
161
+ col_data["renames"].each do |col_name|
162
+ begin
163
+ col_rename = table_obj.column(col_name)
164
+ rescue Errno::ENOENT => e
165
+ next
166
+ end
167
+
168
+ print "Rename #{table_obj.name}.#{col_name} to #{table_obj.name}.#{col_data["name"]}\n" if args["debug"]
169
+ if col_data.is_a?(Hash) and col_data["on_before_rename"]
170
+ col_data["on_before_rename"].call("db" => db, "table" => table_obj, "col" => col_rename, "col_data" => col_data)
171
+ end
172
+
173
+ col_rename.change(col_data)
174
+
175
+ #Change has been made - update cache.
176
+ cols = table_obj.columns
177
+
178
+ if col_data.is_a?(Hash) and col_data["on_after_rename"]
179
+ col_data["on_after_rename"].call("db" => db, "table" => table_obj, "col" => col_rename, "col_data" => col_data)
180
+ end
181
+
182
+ rename_found = true
183
+ break
184
+ end
185
+
186
+ retry if rename_found
187
+ end
188
+
189
+ oncreated = col_data["on_created"]
190
+ col_data.delete("on_created") if col_data["oncreated"]
191
+ col_obj = table_obj.create_columns([col_data])
192
+
193
+ #Change has been made - update cache.
194
+ cols = table_obj.columns
195
+
196
+ oncreated.call("db" => db, "table" => table_obj) if oncreated
197
+ end
198
+ end
199
+ end
200
+
201
+ if table_data["columns_remove"]
202
+ table_data["columns_remove"].each do |column_name, column_data|
203
+ begin
204
+ col_obj = table_obj.column(column_name)
205
+ rescue Errno::ENOENT => e
206
+ next
207
+ end
208
+
209
+ column_data["callback"].call if column_data.is_a?(Hash) and column_data["callback"]
210
+ col_obj.drop
211
+ end
212
+ end
213
+
214
+ if table_data["indexes"]
215
+ table_data["indexes"].each do |index_data|
216
+ if index_data.is_a?(String)
217
+ index_data = {"name" => index_data, "columns" => [index_data]}
218
+ end
219
+
220
+ begin
221
+ index_obj = table_obj.index(index_data["name"])
222
+
223
+ rewrite_index = false
224
+ rewrite_index = true if index_data.key?("unique") and index_data["unique"] != index_obj.unique?
225
+
226
+ if rewrite_index
227
+ index_obj.drop
228
+ table_obj.create_indexes([index_data])
229
+ end
230
+ rescue Errno::ENOENT => e
231
+ table_obj.create_indexes([index_data])
232
+ end
233
+ end
234
+ end
235
+
236
+ if table_data["indexes_remove"]
237
+ table_data["indexes_remove"].each do |index_name, index_data|
238
+ begin
239
+ index_obj = table_obj.index(index_name)
240
+ rescue Errno::ENOENT => e
241
+ next
242
+ end
243
+
244
+ if index_data.is_a?(Hash) and index_data["callback"]
245
+ index_data["callback"].call if index_data["callback"]
246
+ end
247
+
248
+ index_obj.drop
249
+ end
250
+ end
251
+
252
+ rows_init("db" => db, "table" => table_obj, "rows" => table_data["rows"]) if table_data and table_data["rows"]
253
+ rescue Errno::ENOENT => e
254
+ if table_data.key?("renames")
255
+ table_data["renames"].each do |table_name_rename|
256
+ begin
257
+ table_rename = db.tables[table_name_rename.to_sym]
258
+ table_rename.rename(table_name)
259
+ raise Knj::Errors::Retry
260
+ rescue Errno::ENOENT
261
+ next
262
+ end
263
+ end
264
+ end
265
+
266
+ if !table_data.key?("columns")
267
+ print "Notice: Skipping creation of '#{table_name}' because no columns were given in hash.\n"
268
+ next
269
+ end
270
+
271
+ if table_data["on_create"]
272
+ table_data["on_create"].call("db" => db, "table_name" => table_name, "table_data" => table_data)
273
+ end
274
+
275
+ db.tables.create(table_name, table_data)
276
+ table_obj = db.tables[table_name.to_sym]
277
+
278
+ if table_data["on_create_after"]
279
+ table_data["on_create_after"].call("db" => db, "table_name" => table_name, "table_data" => table_data)
280
+ end
281
+
282
+ rows_init("db" => db, "table" => table_obj, "rows" => table_data["rows"]) if table_data["rows"]
283
+ end
284
+ rescue Knj::Errors::Retry
285
+ retry
286
+ end
287
+ end
288
+
289
+ if schema["tables_remove"]
290
+ schema["tables_remove"].each do |table_name, table_data|
291
+ begin
292
+ table_obj = db.tables[table_name.to_sym]
293
+ table_data["callback"].call("db" => db, "table" => table_obj) if table_data.is_a?(Hash) and table_data["callback"]
294
+ table_obj.drop
295
+ rescue Errno::ENOENT => e
296
+ next
297
+ end
298
+ end
299
+ end
300
+
301
+
302
+ #Free cache.
303
+ tables.clear if tables
304
+ tables = nil
305
+ end
306
+
307
+ private
308
+
309
+ #This method checks if certain rows are present in a table based on a hash.
310
+ def rows_init(args)
311
+ db = args["db"]
312
+ table = args["table"]
313
+
314
+ raise "No db given." if !db
315
+ raise "No table given." if !table
316
+
317
+ args["rows"].each do |row_data|
318
+ if row_data["find_by"]
319
+ find_by = row_data["find_by"]
320
+ elsif row_data["data"]
321
+ find_by = row_data["data"]
322
+ else
323
+ raise "Could not figure out the find-by."
324
+ end
325
+
326
+ rows_found = 0
327
+ args["db"].select(table.name, find_by) do |d_rows|
328
+ rows_found += 1
329
+
330
+ if Knj::ArrayExt.hash_diff?(Knj::ArrayExt.hash_sym(row_data["data"]), Knj::ArrayExt.hash_sym(d_rows), {"h2_to_h1" => false})
331
+ print "Data was not right - updating row: #{JSON.generate(row_data["data"])}\n" if args["debug"]
332
+ args["db"].update(table.name, row_data["data"], d_rows)
333
+ end
334
+ end
335
+
336
+ if rows_found == 0
337
+ print "Inserting row: #{JSON.generate(row_data["data"])}\n" if args["debug"]
338
+ table.insert(row_data["data"])
339
+ end
340
+ end
341
+ end
342
+ end
data/include/row.rb ADDED
@@ -0,0 +1,153 @@
1
+ class Baza::Row
2
+ attr_reader :args, :data
3
+
4
+ def is_knj?; return true; end
5
+
6
+ def initialize(args)
7
+ @args = {}
8
+ args.each do |key, value|
9
+ @args[key.to_sym] = value
10
+ end
11
+
12
+ @args[:db] = $db if !@args[:db] and $db and $db.class.to_s == "Baza::Db"
13
+ @args[:objects] = $objects if !@args[:objects] and $objects and $objects.is_a?(Baza::ModelHandler)
14
+ @args[:col_id] = :id if !@args[:col_id]
15
+ raise "No table given." if !@args[:table]
16
+
17
+ if @args[:data] and (@args[:data].is_a?(Integer) or @args[:data].is_a?(Fixnum) or @args[:data].is_a?(String))
18
+ @data = {@args[:col_id].to_sym => @args[:data].to_s}
19
+ self.reload
20
+ elsif @args[:data] and @args[:data].is_a?(Hash)
21
+ @data = {}
22
+ @args[:data].each do |key, value|
23
+ @data[key.to_sym] = value
24
+ end
25
+ elsif @args[:id]
26
+ @data = {}
27
+ @data[@args[:col_id].to_sym] = @args[:id]
28
+ self.reload
29
+ else
30
+ raise ArgumentError.new("Invalid data: #{@args[:data].to_s} (#{@args[:data].class.to_s})")
31
+ end
32
+ end
33
+
34
+ def db
35
+ if !@args[:force_selfdb]
36
+ curthread = Thread.current
37
+ if curthread.is_a?(Knj::Thread) and curthread[:knjappserver] and curthread[:knjappserver][:db]
38
+ return curthread[:knjappserver][:db]
39
+ end
40
+ end
41
+
42
+ return @args[:db]
43
+ end
44
+
45
+ def ob
46
+ return @args[:objects] if @args.key?(:objects)
47
+ return $ob if $ob and $ob.is_a?(Baza::ModelHandler)
48
+ return false
49
+ end
50
+
51
+ alias :objects :ob
52
+
53
+ def reload
54
+ last_id = self.id
55
+ data = self.db.single(@args[:table], {@args[:col_id] => self.id})
56
+ if !data
57
+ raise Errno::ENOENT.new("Could not find any data for the object with ID: '#{last_id}' in the table '#{@args[:table].to_s}'.")
58
+ end
59
+
60
+ @data = {}
61
+ data.each do |key, value|
62
+ @data[key.to_sym] = value
63
+ end
64
+ end
65
+
66
+ def update(newdata)
67
+ self.db.update(@args[:table], newdata, {@args[:col_id] => self.id})
68
+ self.reload
69
+
70
+ if self.ob
71
+ self.ob.call("object" => self, "signal" => "update")
72
+ end
73
+ end
74
+
75
+ def delete
76
+ self.db.delete(@args[:table], {@args[:col_id] => self.id})
77
+ self.destroy
78
+ end
79
+
80
+ def destroy
81
+ @args = nil
82
+ @data = nil
83
+ end
84
+
85
+ def has_key?(key)
86
+ return @data.key?(key.to_sym)
87
+ end
88
+
89
+ def [](key)
90
+ raise "No valid key given." if !key
91
+ raise "No data was loaded on the object? Maybe you are trying to call a deleted object?" if !@data
92
+
93
+ if @data.key?(key)
94
+ return @data[key]
95
+ elsif @data.key?(key.to_sym)
96
+ return @data[key.to_sym]
97
+ elsif @data.key?(key.to_s)
98
+ return @data[key.to_s]
99
+ end
100
+
101
+ raise "No such key: #{key.to_s}."
102
+ end
103
+
104
+ def []=(key, value)
105
+ self.update(key.to_sym => value)
106
+ self.reload
107
+ end
108
+
109
+ def id
110
+ return @data[@args[:col_id]]
111
+ end
112
+
113
+ def title
114
+ if @args[:col_title]
115
+ return @data[@args[:col_title].to_sym]
116
+ end
117
+
118
+ if @data.key?(:title)
119
+ return @data[:title]
120
+ elsif @data.key?(:name)
121
+ return @data[:name]
122
+ end
123
+
124
+ raise "'col_title' has not been set for the class: '#{self.class.to_s}'."
125
+ end
126
+
127
+ alias :name :title
128
+
129
+ def each(&args)
130
+ return @data.each(&args)
131
+ end
132
+
133
+ def to_hash
134
+ return @data.clone
135
+ end
136
+
137
+ def esc(str)
138
+ return self.db.escape(str)
139
+ end
140
+
141
+ def method_missing(*args)
142
+ func_name = args[0].to_s
143
+ if match = func_name.match(/^(\S+)\?$/) and @data.key?(match[1].to_sym)
144
+ if @data[match[1].to_sym] == "1" or @data[match[1].to_sym] == "yes"
145
+ return true
146
+ elsif @data[match[1].to_sym] == "0" or @data[match[1].to_sym] == "no"
147
+ return false
148
+ end
149
+ end
150
+
151
+ raise sprintf("No such method: %s", func_name)
152
+ end
153
+ end