baza 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.rspec +1 -0
- data/Gemfile +18 -0
- data/Gemfile.lock +55 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +19 -0
- data/Rakefile +49 -0
- data/VERSION +1 -0
- data/include/db.rb +784 -0
- data/include/dbtime.rb +35 -0
- data/include/drivers/.DS_Store +0 -0
- data/include/drivers/mysql/mysql.rb +604 -0
- data/include/drivers/mysql/mysql_columns.rb +155 -0
- data/include/drivers/mysql/mysql_indexes.rb +69 -0
- data/include/drivers/mysql/mysql_sqlspecs.rb +5 -0
- data/include/drivers/mysql/mysql_tables.rb +443 -0
- data/include/drivers/sqlite3/libknjdb_java_sqlite3.rb +83 -0
- data/include/drivers/sqlite3/libknjdb_sqlite3_ironruby.rb +69 -0
- data/include/drivers/sqlite3/sqlite3.rb +184 -0
- data/include/drivers/sqlite3/sqlite3_columns.rb +177 -0
- data/include/drivers/sqlite3/sqlite3_indexes.rb +29 -0
- data/include/drivers/sqlite3/sqlite3_sqlspecs.rb +5 -0
- data/include/drivers/sqlite3/sqlite3_tables.rb +449 -0
- data/include/dump.rb +122 -0
- data/include/idquery.rb +109 -0
- data/include/model.rb +873 -0
- data/include/model_custom.rb +153 -0
- data/include/model_handler.rb +957 -0
- data/include/model_handler_sqlhelper.rb +499 -0
- data/include/query_buffer.rb +87 -0
- data/include/revision.rb +342 -0
- data/include/row.rb +153 -0
- data/include/sqlspecs.rb +5 -0
- data/lib/baza.rb +8 -0
- data/spec/baza_spec.rb +286 -0
- data/spec/db_spec_encoding_test_file.txt +1 -0
- data/spec/spec_helper.rb +12 -0
- metadata +215 -0
data/.document
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
# Add dependencies required to use your gem here.
|
3
|
+
# Example:
|
4
|
+
# gem "activesupport", ">= 2.3.5"
|
5
|
+
|
6
|
+
gem "datet"
|
7
|
+
gem "wref"
|
8
|
+
gem "knjrbfw"
|
9
|
+
|
10
|
+
# Add dependencies to develop your gem here.
|
11
|
+
# Include everything needed to run rake, tests, features, etc.
|
12
|
+
group :development do
|
13
|
+
gem "rspec", "~> 2.8.0"
|
14
|
+
gem "rdoc", "~> 3.12"
|
15
|
+
gem "bundler", ">= 1.0.0"
|
16
|
+
gem "jeweler", "~> 1.8.4"
|
17
|
+
gem "sqlite3"
|
18
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
datet (0.0.25)
|
5
|
+
diff-lcs (1.1.3)
|
6
|
+
git (1.2.5)
|
7
|
+
http2 (0.0.13)
|
8
|
+
jeweler (1.8.4)
|
9
|
+
bundler (~> 1.0)
|
10
|
+
git (>= 1.2.5)
|
11
|
+
rake
|
12
|
+
rdoc
|
13
|
+
json (1.7.7)
|
14
|
+
knjrbfw (0.0.104)
|
15
|
+
datet
|
16
|
+
http2
|
17
|
+
php4r
|
18
|
+
ruby_process
|
19
|
+
tsafe
|
20
|
+
wref
|
21
|
+
php4r (0.0.4)
|
22
|
+
datet
|
23
|
+
http2
|
24
|
+
string-strtr
|
25
|
+
rake (10.0.4)
|
26
|
+
rdoc (3.12.2)
|
27
|
+
json (~> 1.4)
|
28
|
+
rspec (2.8.0)
|
29
|
+
rspec-core (~> 2.8.0)
|
30
|
+
rspec-expectations (~> 2.8.0)
|
31
|
+
rspec-mocks (~> 2.8.0)
|
32
|
+
rspec-core (2.8.0)
|
33
|
+
rspec-expectations (2.8.0)
|
34
|
+
diff-lcs (~> 1.1.2)
|
35
|
+
rspec-mocks (2.8.0)
|
36
|
+
ruby_process (0.0.8)
|
37
|
+
tsafe
|
38
|
+
wref
|
39
|
+
sqlite3 (1.3.7)
|
40
|
+
string-strtr (0.0.3)
|
41
|
+
tsafe (0.0.11)
|
42
|
+
wref (0.0.6)
|
43
|
+
|
44
|
+
PLATFORMS
|
45
|
+
ruby
|
46
|
+
|
47
|
+
DEPENDENCIES
|
48
|
+
bundler (>= 1.0.0)
|
49
|
+
datet
|
50
|
+
jeweler (~> 1.8.4)
|
51
|
+
knjrbfw
|
52
|
+
rdoc (~> 3.12)
|
53
|
+
rspec (~> 2.8.0)
|
54
|
+
sqlite3
|
55
|
+
wref
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2013 Kasper Johansen
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
= baza
|
2
|
+
|
3
|
+
Description goes here.
|
4
|
+
|
5
|
+
== Contributing to baza
|
6
|
+
|
7
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
|
8
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
|
9
|
+
* Fork the project.
|
10
|
+
* Start a feature/bugfix branch.
|
11
|
+
* Commit and push until you are happy with your contribution.
|
12
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
13
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
14
|
+
|
15
|
+
== Copyright
|
16
|
+
|
17
|
+
Copyright (c) 2013 Kasper Johansen. See LICENSE.txt for
|
18
|
+
further details.
|
19
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
17
|
+
gem.name = "baza"
|
18
|
+
gem.homepage = "http://github.com/kaspernj/baza"
|
19
|
+
gem.license = "MIT"
|
20
|
+
gem.summary = %Q{A database abstraction layer, model framework and database framework.}
|
21
|
+
gem.description = %Q{A database abstraction layer, model framework and database framework.}
|
22
|
+
gem.email = "kj@gfish.com"
|
23
|
+
gem.authors = ["Kasper Johansen"]
|
24
|
+
# dependencies defined in Gemfile
|
25
|
+
end
|
26
|
+
Jeweler::RubygemsDotOrgTasks.new
|
27
|
+
|
28
|
+
require 'rspec/core'
|
29
|
+
require 'rspec/core/rake_task'
|
30
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
31
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
32
|
+
end
|
33
|
+
|
34
|
+
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
35
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
36
|
+
spec.rcov = true
|
37
|
+
end
|
38
|
+
|
39
|
+
task :default => :spec
|
40
|
+
|
41
|
+
require 'rdoc/task'
|
42
|
+
Rake::RDocTask.new do |rdoc|
|
43
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
44
|
+
|
45
|
+
rdoc.rdoc_dir = 'rdoc'
|
46
|
+
rdoc.title = "baza #{version}"
|
47
|
+
rdoc.rdoc_files.include('README*')
|
48
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
49
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.0
|
data/include/db.rb
ADDED
@@ -0,0 +1,784 @@
|
|
1
|
+
require "knjrbfw"
|
2
|
+
Knj.gem_require([:wref, :datet])
|
3
|
+
|
4
|
+
#A wrapper of several possible database-types.
|
5
|
+
#
|
6
|
+
#===Examples
|
7
|
+
# db = Baza::Db.new(:type => "mysql", :subtype => "mysql2", :db => "mysql", :user => "user", :pass => "password")
|
8
|
+
# mysql_table = db.tables['mysql']
|
9
|
+
# name = mysql_table.name
|
10
|
+
# cols = mysql_table.columns
|
11
|
+
#
|
12
|
+
# db = Baza::Db.new(:type => "sqlite3", :path => "some_db.sqlite3")
|
13
|
+
#
|
14
|
+
# db.q("SELECT * FROM users") do |data|
|
15
|
+
# print data[:name]
|
16
|
+
# end
|
17
|
+
class Baza::Db
|
18
|
+
attr_reader :sep_col, :sep_table, :sep_val, :opts, :conn, :conns, :int_types
|
19
|
+
|
20
|
+
def initialize(opts)
|
21
|
+
self.setOpts(opts) if opts != nil
|
22
|
+
|
23
|
+
@int_types = ["int", "bigint", "tinyint", "smallint", "mediumint"]
|
24
|
+
|
25
|
+
if !@opts[:threadsafe]
|
26
|
+
require "monitor"
|
27
|
+
@mutex = Monitor.new
|
28
|
+
end
|
29
|
+
|
30
|
+
@debug = @opts[:debug]
|
31
|
+
|
32
|
+
self.conn_exec do |driver|
|
33
|
+
@sep_table = driver.sep_table
|
34
|
+
@sep_col = driver.sep_col
|
35
|
+
@sep_val = driver.sep_val
|
36
|
+
@esc_driver = driver
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def args
|
41
|
+
return @opts
|
42
|
+
end
|
43
|
+
|
44
|
+
def setOpts(arr_opts)
|
45
|
+
@opts = {}
|
46
|
+
arr_opts.each do |key, val|
|
47
|
+
@opts[key.to_sym] = val
|
48
|
+
end
|
49
|
+
|
50
|
+
if RUBY_PLATFORM == "java"
|
51
|
+
@opts[:subtype] = "java"
|
52
|
+
elsif @opts[:type] == "sqlite3" and RUBY_PLATFORM.index("mswin32") != nil
|
53
|
+
@opts[:subtype] = "ironruby"
|
54
|
+
end
|
55
|
+
|
56
|
+
@type_cc = "#{@opts[:type].to_s.slice(0, 1).upcase}#{@opts[:type].to_s.slice(1, @opts[:type].to_s.length)}"
|
57
|
+
self.connect
|
58
|
+
end
|
59
|
+
|
60
|
+
#Actually connects to the database. This is useually done automatically.
|
61
|
+
def connect
|
62
|
+
if @opts[:threadsafe]
|
63
|
+
require "#{$knjpath}threadhandler"
|
64
|
+
@conns = Knj::Threadhandler.new
|
65
|
+
|
66
|
+
@conns.on_spawn_new do
|
67
|
+
self.spawn
|
68
|
+
end
|
69
|
+
|
70
|
+
@conns.on_inactive do |data|
|
71
|
+
data[:obj].close
|
72
|
+
end
|
73
|
+
|
74
|
+
@conns.on_activate do |data|
|
75
|
+
data[:obj].reconnect
|
76
|
+
end
|
77
|
+
else
|
78
|
+
@conn = self.spawn
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
#Spawns a new driver (useally done automatically).
|
83
|
+
#===Examples
|
84
|
+
# driver_instance = db.spawn
|
85
|
+
def spawn
|
86
|
+
raise "No type given (#{@opts.keys.join(",")})." if !@opts[:type]
|
87
|
+
rpath = "#{File.dirname(__FILE__)}/#{"drivers/#{@opts[:type]}/#{@opts[:type]}.rb"}"
|
88
|
+
require rpath if (!@opts.key?(:require) or @opts[:require]) and File.exists?(rpath)
|
89
|
+
return Baza::Driver.const_get(@type_cc).new(self)
|
90
|
+
end
|
91
|
+
|
92
|
+
#Registers a driver to the current thread.
|
93
|
+
def get_and_register_thread
|
94
|
+
raise "KnjDB-object is not in threadding mode." if !@conns
|
95
|
+
|
96
|
+
thread_cur = Thread.current
|
97
|
+
tid = self.__id__
|
98
|
+
thread_cur[:knjdb] = {} if !thread_cur[:knjdb]
|
99
|
+
|
100
|
+
if thread_cur[:knjdb][tid]
|
101
|
+
#An object has already been spawned - free that first to avoid endless "used" objects.
|
102
|
+
self.free_thread
|
103
|
+
end
|
104
|
+
|
105
|
+
thread_cur[:knjdb][tid] = @conns.get_and_lock if !thread_cur[:knjdb][tid]
|
106
|
+
|
107
|
+
#If block given then be ensure to free thread after yielding.
|
108
|
+
if block_given?
|
109
|
+
begin
|
110
|
+
yield
|
111
|
+
ensure
|
112
|
+
self.free_thread
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
#Frees the current driver from the current thread.
|
118
|
+
def free_thread
|
119
|
+
thread_cur = Thread.current
|
120
|
+
tid = self.__id__
|
121
|
+
|
122
|
+
if thread_cur[:knjdb] and thread_cur[:knjdb].key?(tid)
|
123
|
+
db = thread_cur[:knjdb][tid]
|
124
|
+
thread_cur[:knjdb].delete(tid)
|
125
|
+
@conns.free(db) if @conns
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
#Clean up various memory-stuff if possible.
|
130
|
+
def clean
|
131
|
+
if @conns
|
132
|
+
@conns.objects.each do |data|
|
133
|
+
data[:object].clean if data[:object].respond_to?("clean")
|
134
|
+
end
|
135
|
+
elsif @conn
|
136
|
+
@conn.clean if @conn.respond_to?("clean")
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
#The all driver-database-connections.
|
141
|
+
def close
|
142
|
+
@conn.close if @conn
|
143
|
+
@conns.destroy if @conns
|
144
|
+
|
145
|
+
@conn = nil
|
146
|
+
@conns = nil
|
147
|
+
end
|
148
|
+
|
149
|
+
#Clones the current database-connection with possible extra arguments.
|
150
|
+
def clone_conn(args = {})
|
151
|
+
conn = Baza::Db.new(@opts.clone.merge(args))
|
152
|
+
|
153
|
+
if block_given?
|
154
|
+
begin
|
155
|
+
yield(conn)
|
156
|
+
ensure
|
157
|
+
conn.close
|
158
|
+
end
|
159
|
+
|
160
|
+
return nil
|
161
|
+
else
|
162
|
+
return conn
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
#Copies the content of the current database to another instance of Baza::Db.
|
167
|
+
def copy_to(db, args = {})
|
168
|
+
data["tables"].each do |table|
|
169
|
+
table_args = nil
|
170
|
+
table_args = args["tables"][table["name"].to_s] if args and args["tables"] and args["tables"][table["name"].to_s]
|
171
|
+
next if table_args and table_args["skip"]
|
172
|
+
table.delete("indexes") if table.key?("indexes") and args["skip_indexes"]
|
173
|
+
db.tables.create(table["name"], table)
|
174
|
+
|
175
|
+
limit_from = 0
|
176
|
+
limit_incr = 1000
|
177
|
+
|
178
|
+
loop do
|
179
|
+
ins_arr = []
|
180
|
+
q_rows = self.select(table["name"], {}, {"limit_from" => limit_from, "limit_to" => limit_incr})
|
181
|
+
while d_rows = q_rows.fetch
|
182
|
+
col_args = nil
|
183
|
+
|
184
|
+
if table_args and table_args["columns"]
|
185
|
+
d_rows.each do |col_name, col_data|
|
186
|
+
col_args = table_args["columns"][col_name.to_s] if table_args and table_args["columns"]
|
187
|
+
d_rows[col_name] = "" if col_args and col_args["empty"]
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
ins_arr << d_rows
|
192
|
+
end
|
193
|
+
|
194
|
+
break if ins_arr.empty?
|
195
|
+
|
196
|
+
db.insert_multi(table["name"], ins_arr)
|
197
|
+
limit_from += limit_incr
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
#Returns the data of this database in a hash.
|
203
|
+
#===Examples
|
204
|
+
# data = db.data
|
205
|
+
# tables_hash = data['tables']
|
206
|
+
def data
|
207
|
+
tables_ret = []
|
208
|
+
tables.list.each do |name, table|
|
209
|
+
tables_ret << table.data
|
210
|
+
end
|
211
|
+
|
212
|
+
return {
|
213
|
+
"tables" => tables_ret
|
214
|
+
}
|
215
|
+
end
|
216
|
+
|
217
|
+
#Simply inserts data into a table.
|
218
|
+
#
|
219
|
+
#===Examples
|
220
|
+
# db.insert(:users, {:name => "John", :lastname => "Doe"})
|
221
|
+
# id = db.insert(:users, {:name => "John", :lastname => "Doe"}, :return_id => true)
|
222
|
+
# sql = db.insert(:users, {:name => "John", :lastname => "Doe"}, :return_sql => true) #=> "INSERT INTO `users` (`name`, `lastname`) VALUES ('John', 'Doe')"
|
223
|
+
def insert(tablename, arr_insert, args = nil)
|
224
|
+
sql = "INSERT INTO #{@sep_table}#{tablename}#{@sep_table}"
|
225
|
+
|
226
|
+
if !arr_insert or arr_insert.empty?
|
227
|
+
#This is the correct syntax for inserting a blank row in MySQL.
|
228
|
+
if @opts[:type].to_s == "mysql"
|
229
|
+
sql << " VALUES ()"
|
230
|
+
elsif @opts[:type].to_s == "sqlite3"
|
231
|
+
sql << " DEFAULT VALUES"
|
232
|
+
else
|
233
|
+
raise "Unknown database-type: '#{@opts[:type]}'."
|
234
|
+
end
|
235
|
+
else
|
236
|
+
sql << " ("
|
237
|
+
|
238
|
+
first = true
|
239
|
+
arr_insert.each do |key, value|
|
240
|
+
if first
|
241
|
+
first = false
|
242
|
+
else
|
243
|
+
sql << ", "
|
244
|
+
end
|
245
|
+
|
246
|
+
sql << "#{@sep_col}#{key}#{@sep_col}"
|
247
|
+
end
|
248
|
+
|
249
|
+
sql << ") VALUES ("
|
250
|
+
|
251
|
+
first = true
|
252
|
+
arr_insert.each do |key, value|
|
253
|
+
if first
|
254
|
+
first = false
|
255
|
+
else
|
256
|
+
sql << ", "
|
257
|
+
end
|
258
|
+
|
259
|
+
sql << self.sqlval(value)
|
260
|
+
end
|
261
|
+
|
262
|
+
sql << ")"
|
263
|
+
end
|
264
|
+
|
265
|
+
return sql if args and args[:return_sql]
|
266
|
+
|
267
|
+
self.conn_exec do |driver|
|
268
|
+
driver.query(sql)
|
269
|
+
return driver.lastID if args and args[:return_id]
|
270
|
+
return nil
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
#Returns the correct SQL-value for the given value. If it is a number, then just the raw number as a string will be returned. nil's will be NULL and strings will have quotes and will be escaped.
|
275
|
+
def sqlval(val)
|
276
|
+
return @conn.sqlval(val) if @conn.respond_to?(:sqlval)
|
277
|
+
|
278
|
+
if val.is_a?(Fixnum) or val.is_a?(Integer)
|
279
|
+
return val.to_s
|
280
|
+
elsif val == nil
|
281
|
+
return "NULL"
|
282
|
+
elsif val.is_a?(Date)
|
283
|
+
return "#{@sep_val}#{Datet.in(val).dbstr(:time => false)}#{@sep_val}"
|
284
|
+
elsif val.is_a?(Time) or val.is_a?(DateTime)
|
285
|
+
return "#{@sep_val}#{Datet.in(val).dbstr}#{@sep_val}"
|
286
|
+
else
|
287
|
+
return "#{@sep_val}#{self.escape(val)}#{@sep_val}"
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
#Simply and optimal insert multiple rows into a table in a single query. Uses the drivers functionality if supported or inserts each row manually.
|
292
|
+
#
|
293
|
+
#===Examples
|
294
|
+
# db.insert_multi(:users, [
|
295
|
+
# {:name => "John", :lastname => "Doe"},
|
296
|
+
# {:name => "Kasper", :lastname => "Johansen"}
|
297
|
+
# ])
|
298
|
+
def insert_multi(tablename, arr_hashes, args = nil)
|
299
|
+
return false if arr_hashes.empty?
|
300
|
+
|
301
|
+
if @esc_driver.respond_to?(:insert_multi)
|
302
|
+
if args and args[:return_sql]
|
303
|
+
return [@esc_driver.insert_multi(tablename, arr_hashes, args)]
|
304
|
+
end
|
305
|
+
|
306
|
+
self.conn_exec do |driver|
|
307
|
+
return driver.insert_multi(tablename, arr_hashes, args)
|
308
|
+
end
|
309
|
+
else
|
310
|
+
ret = [] if args and (args[:return_id] or args[:return_sql])
|
311
|
+
arr_hashes.each do |hash|
|
312
|
+
if ret
|
313
|
+
ret << self.insert(tablename, hash, args)
|
314
|
+
else
|
315
|
+
self.insert(tablename, hash, args)
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
if ret
|
320
|
+
return ret
|
321
|
+
else
|
322
|
+
return nil
|
323
|
+
end
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
#Simple updates rows.
|
328
|
+
#
|
329
|
+
#===Examples
|
330
|
+
# db.update(:users, {:name => "John"}, {:lastname => "Doe"})
|
331
|
+
def update(tablename, arr_update, arr_terms = {}, args = nil)
|
332
|
+
raise "'arr_update' was not a hash." if !arr_update.is_a?(Hash)
|
333
|
+
return false if arr_update.empty?
|
334
|
+
|
335
|
+
sql = ""
|
336
|
+
sql << "UPDATE #{@sep_col}#{tablename}#{@sep_col} SET "
|
337
|
+
|
338
|
+
first = true
|
339
|
+
arr_update.each do |key, value|
|
340
|
+
if first
|
341
|
+
first = false
|
342
|
+
else
|
343
|
+
sql << ", "
|
344
|
+
end
|
345
|
+
|
346
|
+
#Convert dates to valid dbstr.
|
347
|
+
value = self.date_out(value) if value.is_a?(Datet) or value.is_a?(Time)
|
348
|
+
|
349
|
+
sql << "#{@sep_col}#{key}#{@sep_col} = "
|
350
|
+
sql << "#{@sep_val}#{@esc_driver.escape(value)}#{@sep_val}"
|
351
|
+
end
|
352
|
+
|
353
|
+
if arr_terms and arr_terms.length > 0
|
354
|
+
sql << " WHERE #{self.makeWhere(arr_terms)}"
|
355
|
+
end
|
356
|
+
|
357
|
+
return sql if args and args[:return_sql]
|
358
|
+
|
359
|
+
self.conn_exec do |driver|
|
360
|
+
driver.query(sql)
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
364
|
+
#Checks if a given selector exists. If it does, updates it to match data. If not inserts the row.
|
365
|
+
def upsert(table, selector, data)
|
366
|
+
row = self.select(table, selector, "limit" => 1).fetch
|
367
|
+
|
368
|
+
if row
|
369
|
+
self.update(table, data, selector)
|
370
|
+
else
|
371
|
+
self.insert(table, selector.merge(data))
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
375
|
+
#Makes a select from the given arguments: table-name, where-terms and other arguments as limits and orders. Also takes a block to avoid raping of memory.
|
376
|
+
def select(tablename, arr_terms = nil, args = nil, &block)
|
377
|
+
#Set up vars.
|
378
|
+
sql = ""
|
379
|
+
args_q = nil
|
380
|
+
select_sql = "*"
|
381
|
+
|
382
|
+
#Give 'cloned_ubuf' argument to 'q'-method.
|
383
|
+
if args and args[:cloned_ubuf]
|
384
|
+
args_q = {:cloned_ubuf => true}
|
385
|
+
end
|
386
|
+
|
387
|
+
#Set up IDQuery-stuff if that is given in arguments.
|
388
|
+
if args and args[:idquery]
|
389
|
+
if args[:idquery] == true
|
390
|
+
select_sql = "`id`"
|
391
|
+
col = :id
|
392
|
+
else
|
393
|
+
select_sql = "`#{self.esc_col(args[:idquery])}`"
|
394
|
+
col = args[:idquery]
|
395
|
+
end
|
396
|
+
end
|
397
|
+
|
398
|
+
sql = "SELECT #{select_sql} FROM #{@sep_table}#{tablename}#{@sep_table}"
|
399
|
+
|
400
|
+
if arr_terms != nil and !arr_terms.empty?
|
401
|
+
sql << " WHERE #{self.makeWhere(arr_terms)}"
|
402
|
+
end
|
403
|
+
|
404
|
+
if args != nil
|
405
|
+
if args["orderby"]
|
406
|
+
sql << " ORDER BY #{args["orderby"]}"
|
407
|
+
end
|
408
|
+
|
409
|
+
if args["limit"]
|
410
|
+
sql << " LIMIT #{args["limit"]}"
|
411
|
+
end
|
412
|
+
|
413
|
+
if args["limit_from"] and args["limit_to"]
|
414
|
+
raise "'limit_from' was not numeric: '#{args["limit_from"]}'." if !(Float(args["limit_from"]) rescue false)
|
415
|
+
raise "'limit_to' was not numeric: '#{args["limit_to"]}'." if !(Float(args["limit_to"]) rescue false)
|
416
|
+
sql << " LIMIT #{args["limit_from"]}, #{args["limit_to"]}"
|
417
|
+
end
|
418
|
+
end
|
419
|
+
|
420
|
+
#Do IDQuery if given in arguments.
|
421
|
+
if args and args[:idquery]
|
422
|
+
res = Baza::Idquery.new(:db => self, :table => tablename, :query => sql, :col => col, &block)
|
423
|
+
else
|
424
|
+
res = self.q(sql, args_q, &block)
|
425
|
+
end
|
426
|
+
|
427
|
+
#Return result if a block wasnt given.
|
428
|
+
if block
|
429
|
+
return nil
|
430
|
+
else
|
431
|
+
return res
|
432
|
+
end
|
433
|
+
end
|
434
|
+
|
435
|
+
#Returns a single row from a database.
|
436
|
+
#
|
437
|
+
#===Examples
|
438
|
+
# row = db.single(:users, {:lastname => "Doe"})
|
439
|
+
def single(tablename, arr_terms = nil, args = {})
|
440
|
+
args["limit"] = 1
|
441
|
+
|
442
|
+
#Experienced very weird memory leak if this was not done by block. Maybe bug in Ruby 1.9.2? - knj
|
443
|
+
self.select(tablename, arr_terms, args) do |data|
|
444
|
+
return data
|
445
|
+
end
|
446
|
+
|
447
|
+
return false
|
448
|
+
end
|
449
|
+
|
450
|
+
alias :selectsingle :single
|
451
|
+
|
452
|
+
#Deletes rows from the database.
|
453
|
+
#
|
454
|
+
#===Examples
|
455
|
+
# db.delete(:users, {:lastname => "Doe"})
|
456
|
+
def delete(tablename, arr_terms, args = nil)
|
457
|
+
sql = "DELETE FROM #{@sep_table}#{tablename}#{@sep_table}"
|
458
|
+
|
459
|
+
if arr_terms != nil and !arr_terms.empty?
|
460
|
+
sql << " WHERE #{self.makeWhere(arr_terms)}"
|
461
|
+
end
|
462
|
+
|
463
|
+
return sql if args and args[:return_sql]
|
464
|
+
|
465
|
+
self.conn_exec do |driver|
|
466
|
+
driver.query(sql)
|
467
|
+
end
|
468
|
+
|
469
|
+
return nil
|
470
|
+
end
|
471
|
+
|
472
|
+
#Internally used to generate SQL.
|
473
|
+
#
|
474
|
+
#===Examples
|
475
|
+
# sql = db.makeWhere({:lastname => "Doe"}, driver_obj)
|
476
|
+
def makeWhere(arr_terms, driver = nil)
|
477
|
+
sql = ""
|
478
|
+
|
479
|
+
first = true
|
480
|
+
arr_terms.each do |key, value|
|
481
|
+
if first
|
482
|
+
first = false
|
483
|
+
else
|
484
|
+
sql << " AND "
|
485
|
+
end
|
486
|
+
|
487
|
+
if value.is_a?(Array)
|
488
|
+
raise "Array for column '#{key}' was empty." if value.empty?
|
489
|
+
sql << "#{@sep_col}#{key}#{@sep_col} IN (#{Knj::ArrayExt.join(:arr => value, :sep => ",", :surr => "'", :callback => proc{|ele| self.esc(ele)})})"
|
490
|
+
elsif value.is_a?(Hash)
|
491
|
+
raise "Dont know how to handle hash."
|
492
|
+
else
|
493
|
+
sql << "#{@sep_col}#{key}#{@sep_col} = #{@sep_val}#{@esc_driver.escape(value)}#{@sep_val}"
|
494
|
+
end
|
495
|
+
end
|
496
|
+
|
497
|
+
return sql
|
498
|
+
end
|
499
|
+
|
500
|
+
#Returns a driver-object based on the current thread and free driver-objects.
|
501
|
+
#
|
502
|
+
#===Examples
|
503
|
+
# db.conn_exec do |driver|
|
504
|
+
# str = driver.escape('something̈́')
|
505
|
+
# end
|
506
|
+
def conn_exec
|
507
|
+
if tcur = Thread.current and tcur[:knjdb]
|
508
|
+
tid = self.__id__
|
509
|
+
|
510
|
+
if tcur[:knjdb].key?(tid)
|
511
|
+
yield(tcur[:knjdb][tid])
|
512
|
+
return nil
|
513
|
+
end
|
514
|
+
end
|
515
|
+
|
516
|
+
if @conns
|
517
|
+
conn = @conns.get_and_lock
|
518
|
+
|
519
|
+
begin
|
520
|
+
yield(conn)
|
521
|
+
return nil
|
522
|
+
ensure
|
523
|
+
@conns.free(conn)
|
524
|
+
end
|
525
|
+
elsif @conn
|
526
|
+
@mutex.synchronize do
|
527
|
+
yield(@conn)
|
528
|
+
return nil
|
529
|
+
end
|
530
|
+
end
|
531
|
+
|
532
|
+
raise "Could not figure out how to find a driver to use?"
|
533
|
+
end
|
534
|
+
|
535
|
+
#Executes a query and returns the result.
|
536
|
+
#
|
537
|
+
#===Examples
|
538
|
+
# res = db.query('SELECT * FROM users')
|
539
|
+
# while data = res.fetch
|
540
|
+
# print data[:name]
|
541
|
+
# end
|
542
|
+
def query(string)
|
543
|
+
if @debug
|
544
|
+
print "SQL: #{string}\n"
|
545
|
+
|
546
|
+
if @debug.is_a?(Fixnum) and @debug >= 2
|
547
|
+
print caller.join("\n")
|
548
|
+
print "\n"
|
549
|
+
end
|
550
|
+
end
|
551
|
+
|
552
|
+
self.conn_exec do |driver|
|
553
|
+
return driver.query(string)
|
554
|
+
end
|
555
|
+
end
|
556
|
+
|
557
|
+
#Execute an ubuffered query and returns the result.
|
558
|
+
#
|
559
|
+
#===Examples
|
560
|
+
# db.query_ubuf('SELECT * FROM users') do |data|
|
561
|
+
# print data[:name]
|
562
|
+
# end
|
563
|
+
def query_ubuf(string, &block)
|
564
|
+
ret = nil
|
565
|
+
|
566
|
+
self.conn_exec do |driver|
|
567
|
+
ret = driver.query_ubuf(string, &block)
|
568
|
+
end
|
569
|
+
|
570
|
+
if block
|
571
|
+
ret.each(&block)
|
572
|
+
return nil
|
573
|
+
end
|
574
|
+
|
575
|
+
return ret
|
576
|
+
end
|
577
|
+
|
578
|
+
#Clones the connection, executes the given block and closes the connection again.
|
579
|
+
#
|
580
|
+
#===Examples
|
581
|
+
# db.cloned_conn do |conn|
|
582
|
+
# conn.q('SELCET * FROM users') do |data|
|
583
|
+
# print data[:name]
|
584
|
+
# end
|
585
|
+
# end
|
586
|
+
def cloned_conn(args = nil, &block)
|
587
|
+
clone_conn_args = {
|
588
|
+
:threadsafe => false
|
589
|
+
}
|
590
|
+
|
591
|
+
clone_conn_args.merge!(args[:clone_args]) if args and args[:clone_args]
|
592
|
+
dbconn = self.clone_conn(clone_conn_args)
|
593
|
+
|
594
|
+
begin
|
595
|
+
yield(dbconn)
|
596
|
+
ensure
|
597
|
+
dbconn.close
|
598
|
+
end
|
599
|
+
end
|
600
|
+
|
601
|
+
#Executes a query and returns the result. If a block is given the result is iterated over that block instead and it returns nil.
|
602
|
+
#
|
603
|
+
#===Examples
|
604
|
+
# db.q('SELECT * FROM users') do |data|
|
605
|
+
# print data[:name]
|
606
|
+
# end
|
607
|
+
def q(str, args = nil, &block)
|
608
|
+
#If the query should be executed in a new connection unbuffered.
|
609
|
+
if args
|
610
|
+
if args[:cloned_ubuf]
|
611
|
+
raise "No block given." if !block
|
612
|
+
|
613
|
+
self.cloned_conn(:clone_args => args[:clone_args]) do |cloned_conn|
|
614
|
+
ret = cloned_conn.query_ubuf(str)
|
615
|
+
ret.each(&block)
|
616
|
+
end
|
617
|
+
|
618
|
+
return nil
|
619
|
+
else
|
620
|
+
raise "Invalid arguments given: '#{args}'."
|
621
|
+
end
|
622
|
+
end
|
623
|
+
|
624
|
+
ret = self.query(str)
|
625
|
+
|
626
|
+
if block
|
627
|
+
ret.each(&block)
|
628
|
+
return nil
|
629
|
+
end
|
630
|
+
|
631
|
+
return ret
|
632
|
+
end
|
633
|
+
|
634
|
+
#Yields a query-buffer and flushes at the end of the block given.
|
635
|
+
def q_buffer(&block)
|
636
|
+
Baza::QueryBuffer.new(:db => self, &block)
|
637
|
+
return nil
|
638
|
+
end
|
639
|
+
|
640
|
+
#Returns the last inserted ID.
|
641
|
+
#
|
642
|
+
#===Examples
|
643
|
+
# id = db.last_id
|
644
|
+
def lastID
|
645
|
+
self.conn_exec do |driver|
|
646
|
+
return driver.lastID
|
647
|
+
end
|
648
|
+
end
|
649
|
+
|
650
|
+
alias :last_id :lastID
|
651
|
+
|
652
|
+
#Escapes a string to be safe-to-use in a query-string.
|
653
|
+
#
|
654
|
+
#===Examples
|
655
|
+
# db.q("INSERT INTO users (name) VALUES ('#{db.esc('John')}')")
|
656
|
+
def escape(string)
|
657
|
+
return @esc_driver.escape(string)
|
658
|
+
end
|
659
|
+
|
660
|
+
alias :esc :escape
|
661
|
+
|
662
|
+
#Escapes the given string to be used as a column.
|
663
|
+
def esc_col(str)
|
664
|
+
return @esc_driver.esc_col(str)
|
665
|
+
end
|
666
|
+
|
667
|
+
#Escapes the given string to be used as a table.
|
668
|
+
def esc_table(str)
|
669
|
+
return @esc_driver.esc_table(str)
|
670
|
+
end
|
671
|
+
|
672
|
+
#Returns a string which can be used in SQL with the current driver.
|
673
|
+
#===Examples
|
674
|
+
# str = db.date_out(Time.now) #=> "2012-05-20 22:06:09"
|
675
|
+
def date_out(date_obj = Datet.new, args = {})
|
676
|
+
if @esc_driver.respond_to?(:date_out)
|
677
|
+
return @esc_driver.date_out(date_obj, args)
|
678
|
+
end
|
679
|
+
|
680
|
+
return Datet.in(date_obj).dbstr(args)
|
681
|
+
end
|
682
|
+
|
683
|
+
#Takes a valid date-db-string and converts it into a Datet.
|
684
|
+
#===Examples
|
685
|
+
# db.date_in('2012-05-20 22:06:09') #=> 2012-05-20 22:06:09 +0200
|
686
|
+
def date_in(date_obj)
|
687
|
+
if @esc_driver.respond_to?(:date_in)
|
688
|
+
return @esc_driver.date_in(date_obj)
|
689
|
+
end
|
690
|
+
|
691
|
+
return Datet.in(date_obj)
|
692
|
+
end
|
693
|
+
|
694
|
+
#Returns the table-module and spawns it if it isnt already spawned.
|
695
|
+
def tables
|
696
|
+
if !@tables
|
697
|
+
require "#{File.dirname(__FILE__)}/drivers/#{@opts[:type]}/#{@opts[:type]}_tables" if (!@opts.key?(:require) or @opts[:require])
|
698
|
+
@tables = Baza::Driver.const_get(@type_cc).const_get(:Tables).new(
|
699
|
+
:db => self
|
700
|
+
)
|
701
|
+
end
|
702
|
+
|
703
|
+
return @tables
|
704
|
+
end
|
705
|
+
|
706
|
+
#Returns the columns-module and spawns it if it isnt already spawned.
|
707
|
+
def cols
|
708
|
+
if !@cols
|
709
|
+
require "#{File.dirname(__FILE__)}/drivers/#{@opts[:type]}/#{@opts[:type]}_columns" if (!@opts.key?(:require) or @opts[:require])
|
710
|
+
@cols = Baza::Driver.const_get(@type_cc).const_get(:Columns).new(
|
711
|
+
:db => self
|
712
|
+
)
|
713
|
+
end
|
714
|
+
|
715
|
+
return @cols
|
716
|
+
end
|
717
|
+
|
718
|
+
#Returns the index-module and spawns it if it isnt already spawned.
|
719
|
+
def indexes
|
720
|
+
if !@indexes
|
721
|
+
require "#{File.dirname(__FILE__)}/drivers/#{@opts[:type]}/#{@opts[:type]}_indexes" if (!@opts.key?(:require) or @opts[:require])
|
722
|
+
@indexes = Baza::Driver.const_get(@type_cc).const_get(:Indexes).new(
|
723
|
+
:db => self
|
724
|
+
)
|
725
|
+
end
|
726
|
+
|
727
|
+
return @indexes
|
728
|
+
end
|
729
|
+
|
730
|
+
#Returns the SQLSpec-module and spawns it if it isnt already spawned.
|
731
|
+
def sqlspecs
|
732
|
+
if !@sqlspecs
|
733
|
+
require "#{File.dirname(__FILE__)}/drivers/#{@opts[:type]}/#{@opts[:type]}_sqlspecs" if (!@opts.key?(:require) or @opts[:require])
|
734
|
+
@sqlspecs = Baza::Driver.const_get(@type_cc).const_get(:Sqlspecs).new(
|
735
|
+
:db => self
|
736
|
+
)
|
737
|
+
end
|
738
|
+
|
739
|
+
return @sqlspecs
|
740
|
+
end
|
741
|
+
|
742
|
+
#Beings a transaction and commits when the block ends.
|
743
|
+
#
|
744
|
+
#===Examples
|
745
|
+
# db.transaction do |db|
|
746
|
+
# db.insert(:users, {:name => "John"})
|
747
|
+
# db.insert(:users, {:name => "Kasper"})
|
748
|
+
# end
|
749
|
+
def transaction(&block)
|
750
|
+
self.conn_exec do |driver|
|
751
|
+
driver.transaction(&block)
|
752
|
+
end
|
753
|
+
end
|
754
|
+
|
755
|
+
#Optimizes all tables in the database.
|
756
|
+
def optimize(args = nil)
|
757
|
+
STDOUT.puts "Beginning optimization of database." if @debug or (args and args[:debug])
|
758
|
+
self.tables.list do |table|
|
759
|
+
STDOUT.puts "Optimizing table: '#{table.name}'." if @debug or (args and args[:debug])
|
760
|
+
table.optimize
|
761
|
+
end
|
762
|
+
|
763
|
+
return nil
|
764
|
+
end
|
765
|
+
|
766
|
+
#Proxies the method to the driver.
|
767
|
+
#
|
768
|
+
#===Examples
|
769
|
+
# db.method_on_driver
|
770
|
+
def method_missing(method_name, *args)
|
771
|
+
self.conn_exec do |driver|
|
772
|
+
if driver.respond_to?(method_name.to_sym)
|
773
|
+
return driver.send(method_name, *args)
|
774
|
+
end
|
775
|
+
end
|
776
|
+
|
777
|
+
raise "Method not found: #{method_name}"
|
778
|
+
end
|
779
|
+
end
|
780
|
+
|
781
|
+
#Subclass that contains all the drivers as further subclasses.
|
782
|
+
class Baza::Driver
|
783
|
+
|
784
|
+
end
|