sqlite_magic 0.0.1
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/.gitignore +17 -0
- data/.travis.yml +11 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +32 -0
- data/Rakefile +1 -0
- data/lib/sqlite_magic.rb +168 -0
- data/lib/sqlite_magic/version.rb +3 -0
- data/spec/.DS_Store +0 -0
- data/spec/lib/sqlite_magic_spec.rb +335 -0
- data/spec/spec_helper.rb +5 -0
- data/sqlite_magic.gemspec +27 -0
- metadata +124 -0
data/.gitignore
ADDED
data/.travis.yml
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
language: ruby
|
|
2
|
+
rvm:
|
|
3
|
+
# - "1.8.7"
|
|
4
|
+
- "1.9.2"
|
|
5
|
+
- "1.9.3"
|
|
6
|
+
- jruby-18mode # JRuby in 1.8 mode
|
|
7
|
+
- jruby-19mode # JRuby in 1.9 mode
|
|
8
|
+
# - rbx-18mode
|
|
9
|
+
- rbx-19mode
|
|
10
|
+
# uncomment this line if your project needs to run something other than `rake`:
|
|
11
|
+
script: bundle exec rspec spec
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Copyright (c) 2013 Chris Taggart
|
|
2
|
+
|
|
3
|
+
MIT License
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
6
|
+
a copy of this software and associated documentation files (the
|
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
11
|
+
the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be
|
|
14
|
+
included in all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# SqliteMagic
|
|
2
|
+
[](https://travis-ci.org/openc/sqlite_magic)
|
|
3
|
+
Experimental abstraction and refactoring of sqlite utility methods from
|
|
4
|
+
scraperwiki-ruby gem
|
|
5
|
+
Note: Not all functionality has yet been duplicated, and this may not work for
|
|
6
|
+
you. Developed and tested on Ruby 1.9.3
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
Add this line to your application's Gemfile:
|
|
11
|
+
|
|
12
|
+
gem 'sqlite_magic', :git => 'git@github.com:openc/sqlite_magic.git'
|
|
13
|
+
|
|
14
|
+
And then execute:
|
|
15
|
+
|
|
16
|
+
$ bundle
|
|
17
|
+
|
|
18
|
+
Or install it yourself as:
|
|
19
|
+
|
|
20
|
+
$ gem install sqlite_magic
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
TODO: Write usage instructions here
|
|
25
|
+
|
|
26
|
+
## Contributing
|
|
27
|
+
|
|
28
|
+
1. Fork it
|
|
29
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
|
30
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
|
31
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
|
32
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require "bundler/gem_tasks"
|
data/lib/sqlite_magic.rb
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
require 'sqlite_magic/version'
|
|
2
|
+
require 'sqlite3'
|
|
3
|
+
|
|
4
|
+
module SqliteMagic
|
|
5
|
+
|
|
6
|
+
extend self
|
|
7
|
+
|
|
8
|
+
class Error < StandardError;end
|
|
9
|
+
class DatabaseError < Error;end
|
|
10
|
+
class NoSuchTable < Error;end
|
|
11
|
+
|
|
12
|
+
class Connection
|
|
13
|
+
attr_reader :database
|
|
14
|
+
def initialize(db_loc='sqlite.db')
|
|
15
|
+
@database = SQLite3::Database.new(db_loc)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def add_columns(tbl_name, col_names)
|
|
19
|
+
existing_cols = database.table_info(tbl_name).map{ |c| c['name'] }
|
|
20
|
+
missing_cols = col_names.map(&:to_s) - existing_cols
|
|
21
|
+
missing_cols.each do |col_name|
|
|
22
|
+
database.execute("ALTER TABLE #{tbl_name} ADD COLUMN #{col_name}")
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def close
|
|
27
|
+
database.close
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def commit
|
|
31
|
+
database.commit
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def create_table(tbl_name, col_names, unique_keys=nil)
|
|
35
|
+
puts "Now creating new table: #{tbl_name}" if verbose?
|
|
36
|
+
query = unique_keys ? "CREATE TABLE #{tbl_name} (#{col_names.join(',')}, UNIQUE (#{unique_keys.join(',')}))" :
|
|
37
|
+
"CREATE TABLE #{tbl_name} (#{col_names.join(',')})"
|
|
38
|
+
database.execute query
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def execute(query,data=nil)
|
|
42
|
+
raw_response = data ? database.execute2(query, data) : database.execute2(query)
|
|
43
|
+
keys = raw_response.shift # get the keys
|
|
44
|
+
raw_response.map{|e| Hash[keys.zip(e)] }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# This is an (expensive) convenience method to insert a row (for given unique keys), or if the row already exists
|
|
48
|
+
#
|
|
49
|
+
def insert_or_update(uniq_keys, values_hash, tbl_name='ocdata')
|
|
50
|
+
field_names_as_symbol_string = values_hash.keys.map{ |k| ":#{k}" }.join(',') # need to appear as symbols
|
|
51
|
+
|
|
52
|
+
sql_statement = "INSERT INTO #{tbl_name} (#{values_hash.keys.join(',')}) VALUES (#{field_names_as_symbol_string})"
|
|
53
|
+
database.execute(sql_statement, values_hash)
|
|
54
|
+
rescue SQLite3::ConstraintException => e
|
|
55
|
+
unique_key_constraint = uniq_keys.map { |k| "#{k}=:#{k}" }.join(' AND ')
|
|
56
|
+
update_keys = (values_hash.keys - uniq_keys).map { |k| "#{k}=:#{k}" }.join(', ')
|
|
57
|
+
sql_statement = "UPDATE #{tbl_name} SET #{update_keys} WHERE #{unique_key_constraint}"
|
|
58
|
+
database.execute sql_statement, values_hash
|
|
59
|
+
rescue SQLite3::SQLException => e
|
|
60
|
+
puts "Exception (#{e.inspect}) raised: #{sql_statement}" if verbose?
|
|
61
|
+
# defer to save_data, which can handle missing tables, columns etc
|
|
62
|
+
save_data(uniq_keys, values_hash, tbl_name)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# #save data into the database
|
|
66
|
+
def save_data(uniq_keys, values_array, tbl_name)
|
|
67
|
+
values_array = [values_array].flatten(1) # coerce to an array
|
|
68
|
+
all_field_names = values_array.map(&:keys).flatten.uniq
|
|
69
|
+
all_field_names_as_string = all_field_names.join(',')
|
|
70
|
+
all_field_names_as_symbol_string = all_field_names.map{ |k| ":#{k}" }.join(',') # need to appear as symbols
|
|
71
|
+
begin
|
|
72
|
+
values_array.each do |values_hash|
|
|
73
|
+
# mustn't use nil value in unique value due to fact that SQLite considers NULL values to be different from
|
|
74
|
+
# each other in UNIQUE indexes. See http://www.sqlite.org/lang_createindex.html
|
|
75
|
+
raise DatabaseError.new("Data has nil value for unique key. Unique keys are #{uniq_keys}. Offending data: #{values_hash.inspect}") unless uniq_keys.all?{ |k| values_hash[k] }
|
|
76
|
+
sql_query = "INSERT OR REPLACE INTO #{tbl_name} (#{all_field_names_as_string}) VALUES (#{all_field_names_as_symbol_string})"
|
|
77
|
+
database.execute(sql_query, values_hash)
|
|
78
|
+
end
|
|
79
|
+
rescue SQLite3::SQLException => e
|
|
80
|
+
puts "Exception (#{e.inspect}) raised" if verbose?
|
|
81
|
+
case e.message
|
|
82
|
+
when /no such table/
|
|
83
|
+
create_table(tbl_name, all_field_names, uniq_keys)
|
|
84
|
+
retry
|
|
85
|
+
when /has no column/
|
|
86
|
+
add_columns(tbl_name, all_field_names)
|
|
87
|
+
retry
|
|
88
|
+
else
|
|
89
|
+
raise e
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Convenience method that returns true if VERBOSE environmental variable set (at the moment whatever it is set to)
|
|
95
|
+
def verbose?
|
|
96
|
+
ENV['VERBOSE']
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# def buildinitialtable(data)
|
|
100
|
+
# raise "buildinitialtable: no swdatakeys" unless @swdatakeys.length == 0
|
|
101
|
+
# coldef = self.newcolumns(data)
|
|
102
|
+
# raise "buildinitialtable: no coldef" unless coldef.length > 0
|
|
103
|
+
# # coldef = coldef[:1] # just put one column in; the rest could be altered -- to prove it's good
|
|
104
|
+
# scoldef = coldef.map { |col| format("`%s` %s", col[0], col[1]) }.join(",")
|
|
105
|
+
# @db.execute(format("create table main.`%s` (%s)", @swdatatblname, scoldef))
|
|
106
|
+
# end
|
|
107
|
+
# def addnewcolumn(k, vt)
|
|
108
|
+
# @db.execute(format("alter table main.`%s` add column `%s` %s", @swdatatblname, k, vt))
|
|
109
|
+
# end
|
|
110
|
+
# Internal function to check a row of data, convert to right format
|
|
111
|
+
# def ScraperWiki._convdata(unique_keys, scraper_data)
|
|
112
|
+
# if unique_keys
|
|
113
|
+
# for key in unique_keys
|
|
114
|
+
# if !key.kind_of?(String) and !key.kind_of?(Symbol)
|
|
115
|
+
# raise 'unique_keys must each be a string or a symbol, this one is not: ' + key
|
|
116
|
+
# end
|
|
117
|
+
# if !scraper_data.include?(key) and !scraper_data.include?(key.to_sym)
|
|
118
|
+
# raise 'unique_keys must be a subset of data, this one is not: ' + key
|
|
119
|
+
# end
|
|
120
|
+
# if scraper_data[key] == nil and scraper_data[key.to_sym] == nil
|
|
121
|
+
# raise 'unique_key value should not be nil, this one is nil: ' + key
|
|
122
|
+
# end
|
|
123
|
+
# end
|
|
124
|
+
# end
|
|
125
|
+
#
|
|
126
|
+
# jdata = { }
|
|
127
|
+
# scraper_data.each_pair do |key, value|
|
|
128
|
+
# raise 'key must not have blank name' if not key
|
|
129
|
+
#
|
|
130
|
+
# key = key.to_s if key.kind_of?(Symbol)
|
|
131
|
+
# raise 'key must be string or symbol type: ' + key if key.class != String
|
|
132
|
+
# raise 'key must be simple text: ' + key if !/[a-zA-Z0-9_\- ]+$/.match(key)
|
|
133
|
+
#
|
|
134
|
+
# # convert formats
|
|
135
|
+
# if value.kind_of?(Date)
|
|
136
|
+
# value = value.iso8601
|
|
137
|
+
# end
|
|
138
|
+
# if value.kind_of?(Time)
|
|
139
|
+
# # debugger
|
|
140
|
+
# value = value.utc.iso8601
|
|
141
|
+
# raise "internal error, timezone came out as non-UTC while converting to SQLite format" unless value.match(/([+-]00:00|Z)$/)
|
|
142
|
+
# value.gsub!(/([+-]00:00|Z)$/, '')
|
|
143
|
+
# end
|
|
144
|
+
# if ![Fixnum, Float, String, TrueClass, FalseClass, NilClass].include?(value.class)
|
|
145
|
+
# value = value.to_s
|
|
146
|
+
# end
|
|
147
|
+
#
|
|
148
|
+
# jdata[key] = value
|
|
149
|
+
# end
|
|
150
|
+
# return jdata
|
|
151
|
+
# end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
# When deciding on the location of the SQLite databases we need to set the directory relative to the directory
|
|
156
|
+
# of the file/app that includes the gem, not the gem itself.
|
|
157
|
+
# Doing it this way, and setting a class variable feels ugly, but this appears to be difficult in Ruby, esp as the
|
|
158
|
+
# file may ultimately be called by another process, e.g. the main OpenCorporates app or the console, whose main
|
|
159
|
+
# directory is unrelated to where the databases are stored (which means we can't use Dir.pwd etc). The only time
|
|
160
|
+
# we know about the directory is when the module is called to extend the file, and we capture that in the
|
|
161
|
+
# @app_directory class variable
|
|
162
|
+
# def self.extended(obj)
|
|
163
|
+
# path, = caller[0].partition(":")
|
|
164
|
+
# @@app_directory = File.dirname(path)
|
|
165
|
+
# end
|
|
166
|
+
|
|
167
|
+
end
|
|
168
|
+
|
data/spec/.DS_Store
ADDED
|
Binary file
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
require_relative '../spec_helper'
|
|
2
|
+
require 'sqlite_magic'
|
|
3
|
+
|
|
4
|
+
describe SqliteMagic do
|
|
5
|
+
it "should define SqliteMagicError exception as subclass of StandardError" do
|
|
6
|
+
SqliteMagic::Error.superclass.should be StandardError
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
it "should define DatabaseError exception as subclass of Error" do
|
|
10
|
+
SqliteMagic::DatabaseError.superclass.should be SqliteMagic::Error
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it "should define InvalidDataError exception as subclass of Error" do
|
|
14
|
+
SqliteMagic::NoSuchTable.superclass.should be SqliteMagic::Error
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
describe SqliteMagic::Connection do
|
|
18
|
+
before do
|
|
19
|
+
@dummy_db = stub('dummy_sqlite3_database')
|
|
20
|
+
SQLite3::Database.stub(:new).and_return(@dummy_db)
|
|
21
|
+
@connection = SqliteMagic::Connection.new
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
describe "initialisation" do
|
|
25
|
+
it "should return a type of connection" do
|
|
26
|
+
connection = SqliteMagic::Connection.new
|
|
27
|
+
connection.should be_kind_of SqliteMagic::Connection
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it "should open up a Sqlite3 database with default name" do
|
|
31
|
+
SQLite3::Database.should_receive(:new).with('sqlite.db').and_return(@dummy_db)
|
|
32
|
+
connection = SqliteMagic::Connection.new
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it "should use given db_name when setting up Sqlite3" do
|
|
36
|
+
SQLite3::Database.should_receive(:new).with('path/to/mynew.db').and_return(@dummy_db)
|
|
37
|
+
connection = SqliteMagic::Connection.new('path/to/mynew.db')
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it "should store Sqlite3 database in @database instance variable" do
|
|
41
|
+
connection = SqliteMagic::Connection.new
|
|
42
|
+
connection.instance_variable_get(:@database).should == @dummy_db
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it "should have #database accessor" do
|
|
47
|
+
SqliteMagic::Connection.new.database.should == @dummy_db
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
describe "#execute" do
|
|
51
|
+
before do
|
|
52
|
+
response_data = [['heading 1', 'heading 2', 'heading 3'],
|
|
53
|
+
['some val 1', 'some val 2', 'some val 3'],
|
|
54
|
+
['another val 1', 'another val 2', 'another val 3']]
|
|
55
|
+
@dummy_db.stub(:execute2).and_return(response_data)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it "should use #execute2 to run query on database" do
|
|
59
|
+
@dummy_db.should_receive(:execute2).with('some query',[:foo])
|
|
60
|
+
|
|
61
|
+
@connection.execute('some query',[:foo])
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
it "should return array of hashes" do
|
|
65
|
+
result = @connection.execute('some query',[:foo])
|
|
66
|
+
result.should be_kind_of Array
|
|
67
|
+
result.first.should be_kind_of Hash
|
|
68
|
+
result.size.should == 2
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
it "should use map results to hash with headings as keys" do
|
|
72
|
+
result = @connection.execute('some query',[:foo])
|
|
73
|
+
result.first['heading 1'].should == 'some val 1'
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
context 'and only headings returned' do
|
|
77
|
+
it "should return empty array" do
|
|
78
|
+
@dummy_db.stub(:execute2).and_return([['foo1','foo2']])
|
|
79
|
+
@connection.execute('some query',[:foo]).should == []
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
context 'and nil passed as second argument' do
|
|
84
|
+
it "should not pass bind vars to #execute2" do
|
|
85
|
+
@dummy_db.should_receive(:execute2).with('some query')
|
|
86
|
+
|
|
87
|
+
@connection.execute('some query',nil)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
describe '#save_data' do
|
|
93
|
+
before do
|
|
94
|
+
@connection = SqliteMagic::Connection.new
|
|
95
|
+
@data = [{:foo => 'bar', :foo2 => 'bar2', :foo3 => 'bar3'},
|
|
96
|
+
{:foo2 => 'baz2', :foo3 => 'baz3', :foo4 => 'baz4'}]
|
|
97
|
+
@unique_keys = [:foo2,:foo3]
|
|
98
|
+
@expected_query_1 = "INSERT OR REPLACE INTO foo_table (foo,foo2,foo3,foo4) VALUES (:foo,:foo2,:foo3,:foo4)"
|
|
99
|
+
@expected_query_2 = "INSERT OR REPLACE INTO foo_table (foo,foo2,foo3,foo4) VALUES (:foo,:foo2,:foo3,:foo4)"
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
it 'should insert each data hash using all given field names' do
|
|
103
|
+
@dummy_db.should_receive(:execute).with(@expected_query_1, @data[0])
|
|
104
|
+
@dummy_db.should_receive(:execute).with(@expected_query_2, @data[1])
|
|
105
|
+
|
|
106
|
+
@connection.save_data(@unique_keys, @data, 'foo_table')
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
context 'and datum is a single hash' do
|
|
110
|
+
it 'should save the hash' do
|
|
111
|
+
@expected_query_1 = "INSERT OR REPLACE INTO foo_table (foo,foo2,foo3) VALUES (:foo,:foo2,:foo3)"
|
|
112
|
+
@dummy_db.should_receive(:execute).with(@expected_query_1, @data.first)
|
|
113
|
+
@connection.save_data(@unique_keys, @data.first, 'foo_table')
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
context 'and table does not exist' do
|
|
118
|
+
before do
|
|
119
|
+
@dummy_db.stub(:execute) # default
|
|
120
|
+
# raise just once
|
|
121
|
+
@dummy_db.should_receive(:execute).with(@expected_query_1, @data[0]).
|
|
122
|
+
and_raise(SQLite3::SQLException.new("no such table: foo_table") )
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
it 'should create table using all field names and unique keys' do
|
|
126
|
+
@connection.should_receive(:create_table).with('foo_table', [:foo,:foo2,:foo3, :foo4], @unique_keys)
|
|
127
|
+
@connection.save_data(@unique_keys, @data, 'foo_table')
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
it 'should insert data' do
|
|
131
|
+
@connection.stub(:create_table)
|
|
132
|
+
@dummy_db.should_receive(:execute).with(@expected_query_2, @data[1])
|
|
133
|
+
|
|
134
|
+
@connection.save_data(@unique_keys, @data, 'foo_table')
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
context 'and some columns do not exist' do
|
|
139
|
+
before do
|
|
140
|
+
@dummy_db.stub(:execute) # default
|
|
141
|
+
# raise just once
|
|
142
|
+
@dummy_db.should_receive(:execute).with(@expected_query_1, @data[0]).
|
|
143
|
+
and_raise(SQLite3::SQLException.new("table mynewtable has no column named foo") )
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
it 'should create missing fields using all field names and unique keys' do
|
|
147
|
+
@connection.should_receive(:add_columns).with('foo_table', [:foo,:foo2,:foo3, :foo4])
|
|
148
|
+
@connection.save_data(@unique_keys, @data, 'foo_table')
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
it 'should insert data' do
|
|
152
|
+
@connection.stub(:add_columns)
|
|
153
|
+
@dummy_db.should_receive(:execute).with(@expected_query_2, @data[1])
|
|
154
|
+
|
|
155
|
+
@connection.save_data(@unique_keys, @data, 'foo_table')
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
context 'and some other error is raised' do
|
|
160
|
+
before do
|
|
161
|
+
@dummy_db.stub(:execute) # default
|
|
162
|
+
# raise just once
|
|
163
|
+
@dummy_db.should_receive(:execute).with(@expected_query_1, @data[0]).
|
|
164
|
+
and_raise(SQLite3::SQLException.new("something else has gone wrong") )
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
it 'should raise error' do
|
|
168
|
+
lambda { @connection.save_data(@unique_keys, @data, 'foo_table') }.should raise_error(SQLite3::SQLException)
|
|
169
|
+
@connection.save_data(@unique_keys, @data, 'foo_table')
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
context 'and data for unique keys is nil' do
|
|
174
|
+
it 'should raise DatabaseError' do
|
|
175
|
+
@data.first[:foo2] = nil
|
|
176
|
+
lambda { @connection.save_data(@unique_keys, @data, 'foo_table') }.should raise_error(SqliteMagic::DatabaseError, /unique key.*foo2/)
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
describe '#create_table' do
|
|
182
|
+
it 'should create default table using given field names' do
|
|
183
|
+
expected_query = "CREATE TABLE some_table (foo,bar,baz)"
|
|
184
|
+
@dummy_db.should_receive(:execute).with(expected_query)
|
|
185
|
+
@connection.create_table(:some_table, [:foo,:bar,:baz])
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
context 'and unique keys are given' do
|
|
189
|
+
it 'should add constraint for given keys' do
|
|
190
|
+
expected_query = "CREATE TABLE some_table (foo,bar,baz, UNIQUE (foo,baz))"
|
|
191
|
+
@dummy_db.should_receive(:execute).with(expected_query)
|
|
192
|
+
@connection.create_table(:some_table, [:foo,:bar,:baz], [:foo,:baz])
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
describe "add_columns" do
|
|
198
|
+
before do
|
|
199
|
+
@table_info = [{"cid"=>0, "name"=>"bar", "type"=>"", "notnull"=>0, "dflt_value"=>nil, "pk"=>0},
|
|
200
|
+
{"cid"=>1, "name"=>"rssd_id", "type"=>"", "notnull"=>0, "dflt_value"=>nil, "pk"=>0}]
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
it 'should get table info' do
|
|
204
|
+
@dummy_db.stub(:execute)
|
|
205
|
+
@dummy_db.should_receive(:table_info).with(:foo_table).and_return(@table_info)
|
|
206
|
+
@connection.add_columns(:foo_table, [:foo,:bar,:baz])
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
it 'should add columns that arent there already' do
|
|
210
|
+
@dummy_db.stub(:table_info).and_return(@table_info)
|
|
211
|
+
@dummy_db.should_receive(:execute).with('ALTER TABLE foo_table ADD COLUMN foo')
|
|
212
|
+
@dummy_db.should_receive(:execute).with('ALTER TABLE foo_table ADD COLUMN baz')
|
|
213
|
+
@connection.add_columns(:foo_table, [:foo,:bar,:baz])
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
describe '#insert_or_update' do
|
|
218
|
+
before do
|
|
219
|
+
@datum = {:foo => 'bar', :foo2 => 'bar2', :foo3 => 'bar3', :foo4 => 'bar4'}
|
|
220
|
+
@unique_keys = [:foo2,:foo3]
|
|
221
|
+
@expected_query = "INSERT INTO foo_table (foo,foo2,foo3,foo4) VALUES (:foo,:foo2,:foo3,:foo4)"
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
it 'should insert data' do
|
|
225
|
+
@dummy_db.should_receive(:execute).with(@expected_query, @datum)
|
|
226
|
+
@connection.insert_or_update(@unique_keys, @datum, 'foo_table')
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
it 'should not update data' do
|
|
230
|
+
@dummy_db.should_not_receive(:execute).with(/UPDATE/, anything)
|
|
231
|
+
@connection.insert_or_update(@unique_keys, @datum, 'foo_table')
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
context 'and no table name given' do
|
|
235
|
+
before do
|
|
236
|
+
@expected_query = "INSERT INTO ocdata (foo,foo2,foo3,foo4) VALUES (:foo,:foo2,:foo3,:foo4)"
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
it 'should use ocdata table by default' do
|
|
240
|
+
@dummy_db.should_receive(:execute).with(@expected_query, @datum)
|
|
241
|
+
@connection.insert_or_update(@unique_keys, @datum)
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
context 'and data already exists' do
|
|
246
|
+
before do
|
|
247
|
+
@dummy_db.should_receive(:execute).with(/INSERT/, anything).and_raise(SQLite3::ConstraintException.new('constraint failed'))
|
|
248
|
+
@expected_update_query = "UPDATE foo_table SET foo=:foo, foo4=:foo4 WHERE foo2=:foo2 AND foo3=:foo3"
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
it 'should update given columns dependent on unique keys' do
|
|
252
|
+
@dummy_db.should_receive(:execute).with(@expected_update_query, @datum)
|
|
253
|
+
@connection.insert_or_update(@unique_keys, @datum, 'foo_table')
|
|
254
|
+
end
|
|
255
|
+
context 'and no table name given' do
|
|
256
|
+
before do
|
|
257
|
+
@expected_update_query = "UPDATE ocdata SET foo=:foo, foo4=:foo4 WHERE foo2=:foo2 AND foo3=:foo3"
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
it 'should use ocdata table by default' do
|
|
261
|
+
@dummy_db.should_receive(:execute).with(@expected_update_query, @datum)
|
|
262
|
+
@connection.insert_or_update(@unique_keys, @datum)
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
context 'and SQLite3::SQLException raised' do
|
|
269
|
+
before do
|
|
270
|
+
@dummy_db.stub(:execute) # default
|
|
271
|
+
# raise just once
|
|
272
|
+
@dummy_db.should_receive(:execute).
|
|
273
|
+
and_raise(SQLite3::SQLException )
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
it 'should defer to save_data' do
|
|
277
|
+
@connection.stub(:create_table)
|
|
278
|
+
@connection.should_receive(:save_data).with(@unique_keys, @datum, 'foo_table')
|
|
279
|
+
|
|
280
|
+
@connection.insert_or_update(@unique_keys, @datum, 'foo_table')
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
describe '#verbose?' do
|
|
286
|
+
it 'should return false if ENV["VERBOSE"] not set' do
|
|
287
|
+
@connection.verbose?.should be_false
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
it 'should return false if ENV["VERBOSE"] set' do
|
|
291
|
+
ENV["VERBOSE"] = 'foo'
|
|
292
|
+
@connection.verbose?.should be_true
|
|
293
|
+
ENV["VERBOSE"] = nil # reset
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
describe '#close' do
|
|
298
|
+
it 'should close database' do
|
|
299
|
+
@dummy_db.should_receive(:close)
|
|
300
|
+
@connection.close
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
describe '#commit' do
|
|
305
|
+
it 'should send commit to database' do
|
|
306
|
+
@dummy_db.should_receive(:commit)
|
|
307
|
+
@connection.commit
|
|
308
|
+
end
|
|
309
|
+
end
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
# before do
|
|
313
|
+
# @dummy_db = stub('database_connection')
|
|
314
|
+
# SqliteMagic.stub(:database).and_return(@dummy_db)
|
|
315
|
+
# end
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
# describe '#database_file' do
|
|
319
|
+
# it 'should return file based on class and in db directory in working directory' do
|
|
320
|
+
# expected_file = File.expand_path(File.join(File.dirname(__FILE__),'..','db','foobot.db'))
|
|
321
|
+
# File.expand_path(SqliteMagic.database_file).should == expected_file
|
|
322
|
+
# end
|
|
323
|
+
# end
|
|
324
|
+
|
|
325
|
+
# end
|
|
326
|
+
#
|
|
327
|
+
# describe '#unlock_database' do
|
|
328
|
+
# it "should start and end transaction on database" do
|
|
329
|
+
# @dummy_db.should_receive(:execute).with('BEGIN TRANSACTION; END;')
|
|
330
|
+
# SqliteMagic.unlock_database
|
|
331
|
+
# end
|
|
332
|
+
# end
|
|
333
|
+
#
|
|
334
|
+
|
|
335
|
+
end
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
+
require 'sqlite_magic/version'
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |spec|
|
|
7
|
+
spec.name = "sqlite_magic"
|
|
8
|
+
spec.version = SqliteMagic::VERSION
|
|
9
|
+
spec.authors = ["Chris Taggart"]
|
|
10
|
+
spec.email = ["info@opencorporates.com"]
|
|
11
|
+
spec.description = %q{Sprinkles some magic onto Sqlite3 gem. Sort of extracted from Scraperwiki gem}
|
|
12
|
+
spec.summary = %q{Sprinkles some magic onto Sqlite3 gem}
|
|
13
|
+
spec.homepage = ""
|
|
14
|
+
spec.license = "MIT"
|
|
15
|
+
|
|
16
|
+
spec.files = `git ls-files`.split($/)
|
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
|
19
|
+
spec.require_paths = ["lib"]
|
|
20
|
+
|
|
21
|
+
spec.add_dependency "sqlite3"
|
|
22
|
+
|
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
|
24
|
+
spec.add_development_dependency "rake"
|
|
25
|
+
spec.add_development_dependency "rspec"
|
|
26
|
+
|
|
27
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: sqlite_magic
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
prerelease: false
|
|
5
|
+
segments:
|
|
6
|
+
- 0
|
|
7
|
+
- 0
|
|
8
|
+
- 1
|
|
9
|
+
version: 0.0.1
|
|
10
|
+
platform: ruby
|
|
11
|
+
authors:
|
|
12
|
+
- Chris Taggart
|
|
13
|
+
autorequire:
|
|
14
|
+
bindir: bin
|
|
15
|
+
cert_chain: []
|
|
16
|
+
|
|
17
|
+
date: 2013-07-03 00:00:00 +01:00
|
|
18
|
+
default_executable:
|
|
19
|
+
dependencies:
|
|
20
|
+
- !ruby/object:Gem::Dependency
|
|
21
|
+
name: sqlite3
|
|
22
|
+
prerelease: false
|
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
|
24
|
+
requirements:
|
|
25
|
+
- - ">="
|
|
26
|
+
- !ruby/object:Gem::Version
|
|
27
|
+
segments:
|
|
28
|
+
- 0
|
|
29
|
+
version: "0"
|
|
30
|
+
type: :runtime
|
|
31
|
+
version_requirements: *id001
|
|
32
|
+
- !ruby/object:Gem::Dependency
|
|
33
|
+
name: bundler
|
|
34
|
+
prerelease: false
|
|
35
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ~>
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
segments:
|
|
40
|
+
- 1
|
|
41
|
+
- 3
|
|
42
|
+
version: "1.3"
|
|
43
|
+
type: :development
|
|
44
|
+
version_requirements: *id002
|
|
45
|
+
- !ruby/object:Gem::Dependency
|
|
46
|
+
name: rake
|
|
47
|
+
prerelease: false
|
|
48
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
|
49
|
+
requirements:
|
|
50
|
+
- - ">="
|
|
51
|
+
- !ruby/object:Gem::Version
|
|
52
|
+
segments:
|
|
53
|
+
- 0
|
|
54
|
+
version: "0"
|
|
55
|
+
type: :development
|
|
56
|
+
version_requirements: *id003
|
|
57
|
+
- !ruby/object:Gem::Dependency
|
|
58
|
+
name: rspec
|
|
59
|
+
prerelease: false
|
|
60
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
|
61
|
+
requirements:
|
|
62
|
+
- - ">="
|
|
63
|
+
- !ruby/object:Gem::Version
|
|
64
|
+
segments:
|
|
65
|
+
- 0
|
|
66
|
+
version: "0"
|
|
67
|
+
type: :development
|
|
68
|
+
version_requirements: *id004
|
|
69
|
+
description: Sprinkles some magic onto Sqlite3 gem. Sort of extracted from Scraperwiki gem
|
|
70
|
+
email:
|
|
71
|
+
- info@opencorporates.com
|
|
72
|
+
executables: []
|
|
73
|
+
|
|
74
|
+
extensions: []
|
|
75
|
+
|
|
76
|
+
extra_rdoc_files: []
|
|
77
|
+
|
|
78
|
+
files:
|
|
79
|
+
- .gitignore
|
|
80
|
+
- .travis.yml
|
|
81
|
+
- Gemfile
|
|
82
|
+
- LICENSE.txt
|
|
83
|
+
- README.md
|
|
84
|
+
- Rakefile
|
|
85
|
+
- lib/sqlite_magic.rb
|
|
86
|
+
- lib/sqlite_magic/version.rb
|
|
87
|
+
- spec/.DS_Store
|
|
88
|
+
- spec/lib/sqlite_magic_spec.rb
|
|
89
|
+
- spec/spec_helper.rb
|
|
90
|
+
- sqlite_magic.gemspec
|
|
91
|
+
has_rdoc: true
|
|
92
|
+
homepage: ""
|
|
93
|
+
licenses:
|
|
94
|
+
- MIT
|
|
95
|
+
post_install_message:
|
|
96
|
+
rdoc_options: []
|
|
97
|
+
|
|
98
|
+
require_paths:
|
|
99
|
+
- lib
|
|
100
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
101
|
+
requirements:
|
|
102
|
+
- - ">="
|
|
103
|
+
- !ruby/object:Gem::Version
|
|
104
|
+
segments:
|
|
105
|
+
- 0
|
|
106
|
+
version: "0"
|
|
107
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
108
|
+
requirements:
|
|
109
|
+
- - ">="
|
|
110
|
+
- !ruby/object:Gem::Version
|
|
111
|
+
segments:
|
|
112
|
+
- 0
|
|
113
|
+
version: "0"
|
|
114
|
+
requirements: []
|
|
115
|
+
|
|
116
|
+
rubyforge_project:
|
|
117
|
+
rubygems_version: 1.3.6
|
|
118
|
+
signing_key:
|
|
119
|
+
specification_version: 3
|
|
120
|
+
summary: Sprinkles some magic onto Sqlite3 gem
|
|
121
|
+
test_files:
|
|
122
|
+
- spec/.DS_Store
|
|
123
|
+
- spec/lib/sqlite_magic_spec.rb
|
|
124
|
+
- spec/spec_helper.rb
|