baza 0.0.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.
@@ -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