arc 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 +9 -0
- data/.rspec +1 -0
- data/Gemfile +21 -0
- data/Rakefile +35 -0
- data/arc.gemspec +20 -0
- data/lib/arc.rb +13 -0
- data/lib/arc/casting.rb +66 -0
- data/lib/arc/data_stores.rb +9 -0
- data/lib/arc/data_stores/abstract/arel_compatibility.rb +50 -0
- data/lib/arc/data_stores/abstract/object_definitions.rb +55 -0
- data/lib/arc/data_stores/abstract/store.rb +55 -0
- data/lib/arc/data_stores/mysql/object_definitions.rb +54 -0
- data/lib/arc/data_stores/mysql/store.rb +51 -0
- data/lib/arc/data_stores/postgres/object_definitions.rb +70 -0
- data/lib/arc/data_stores/postgres/store.rb +38 -0
- data/lib/arc/data_stores/sqlite/object_definitions.rb +51 -0
- data/lib/arc/data_stores/sqlite/store.rb +43 -0
- data/lib/arc/quoting.rb +91 -0
- data/lib/arc/reactor.rb +29 -0
- data/lib/arc/version.rb +3 -0
- data/spec/casting_spec.rb +74 -0
- data/spec/data_stores/abstract/arel_compatibility_spec.rb +63 -0
- data/spec/data_stores/abstract/object_definitions_spec.rb +75 -0
- data/spec/data_stores/abstract/store_spec.rb +59 -0
- data/spec/data_stores/integration/crud_spec.rb +79 -0
- data/spec/data_stores/integration/schema_spec.rb +47 -0
- data/spec/data_stores/mysql/store_spec.rb +11 -0
- data/spec/data_stores/postgres/store_spec.rb +11 -0
- data/spec/data_stores/sqlite/store_spec.rb +11 -0
- data/spec/data_stores_spec.rb +15 -0
- data/spec/quoting_spec.rb +97 -0
- data/spec/spec_helper.rb +81 -0
- data/spec/support/config.yml +16 -0
- data/spec/support/resources/rails.png +0 -0
- data/spec/support/schemas/drop_mysql.sql +1 -0
- data/spec/support/schemas/drop_postgres.sql +1 -0
- data/spec/support/schemas/drop_sqlite.sql +1 -0
- data/spec/support/schemas/mysql.sql +10 -0
- data/spec/support/schemas/postgres.sql +12 -0
- data/spec/support/schemas/sqlite.sql +12 -0
- metadata +105 -0
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
-d
|
data/Gemfile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
#this needs to get added to gemspec eventually
|
3
|
+
gem 'q', :path => '~/Projects/jacobsimeon/q'
|
4
|
+
|
5
|
+
group :test do
|
6
|
+
gem 'arel', :path => "../arel"
|
7
|
+
gem 'rspec'
|
8
|
+
gem 'simplecov', '>= 0.4.0', :require => false
|
9
|
+
gem 'ruby-debug19', :require => 'ruby-debug'
|
10
|
+
group :sqlite do
|
11
|
+
gem 'sqlite3'
|
12
|
+
end
|
13
|
+
group :postgres do
|
14
|
+
gem 'pg'
|
15
|
+
end
|
16
|
+
group :mysql do
|
17
|
+
gem 'mysql2'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
gemspec
|
data/Rakefile
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
task :test do
|
3
|
+
['sqlite', 'postgres', 'mysql'].each do |environment|
|
4
|
+
puts "Running tests for environment #{environment}"
|
5
|
+
ENV['ARC_ENV'] = environment
|
6
|
+
system 'rspec spec'
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
task :setup do
|
11
|
+
#setup postgres
|
12
|
+
`createdb arc_development -O jacob`
|
13
|
+
puts "Setup complete"
|
14
|
+
end
|
15
|
+
|
16
|
+
namespace :test do
|
17
|
+
task :adapter, :env do |task, args|
|
18
|
+
ENV['ARC_ENV'] = args.env
|
19
|
+
system 'rspec spec/data_stores/integration/crud_spec.rb spec/data_stores/integration/schema_spec.rb'
|
20
|
+
end
|
21
|
+
task :docs do
|
22
|
+
`rspec spec -f h > doc/spec.html`
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
#example use of passing arguments to rake
|
27
|
+
# task :say, :word, :person do |task, args|
|
28
|
+
# puts task
|
29
|
+
# puts '"args.word"'
|
30
|
+
# puts "\t- #{args.person}"
|
31
|
+
# end
|
32
|
+
# $ rake say[hello,"Jacob Morris"]
|
33
|
+
# >> say
|
34
|
+
# >> "hello"
|
35
|
+
# >> - Jacob Morris
|
data/arc.gemspec
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "arc/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "arc"
|
7
|
+
s.version = Arc::VERSION
|
8
|
+
s.authors = ["Jacob Morris"]
|
9
|
+
s.email = ["jacob.s.morris@gmail.com"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{A lightweight driver for multiple sql backends}
|
12
|
+
s.description = %q{Compatible with mysql, sqlite, and postgres}
|
13
|
+
|
14
|
+
s.rubyforge_project = "arc"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
end
|
data/lib/arc.rb
ADDED
data/lib/arc/casting.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
module Arc
|
2
|
+
module Casting
|
3
|
+
class CannotCastValueError < StandardError; end
|
4
|
+
|
5
|
+
TYPES = {
|
6
|
+
integer: 'integer',
|
7
|
+
int: 'integer',
|
8
|
+
char: 'string' ,
|
9
|
+
varchar: 'string' ,
|
10
|
+
binary: 'binary' ,
|
11
|
+
date: 'date' ,
|
12
|
+
datetime: 'time' ,
|
13
|
+
time: 'time' ,
|
14
|
+
bool: 'boolean',
|
15
|
+
boolean: 'boolean',
|
16
|
+
bit: 'boolean',
|
17
|
+
float: 'float' ,
|
18
|
+
}
|
19
|
+
|
20
|
+
CAST_METHODS = Hash.new do |cast_methods, key|
|
21
|
+
method = "cast_#{TYPES[key.to_sym]}"
|
22
|
+
cast_methods[key] = method
|
23
|
+
end
|
24
|
+
|
25
|
+
def cast value, type
|
26
|
+
method = CAST_METHODS[type]
|
27
|
+
return send method, value if respond_to? method, :include_private
|
28
|
+
return cast_string value
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
def cast_string value
|
33
|
+
value.to_s
|
34
|
+
end
|
35
|
+
def cast_integer value
|
36
|
+
value.to_i
|
37
|
+
end
|
38
|
+
def cast_float value
|
39
|
+
value.to_f
|
40
|
+
end
|
41
|
+
def cast_time value
|
42
|
+
Time.parse value
|
43
|
+
end
|
44
|
+
def cast_date value
|
45
|
+
Date.parse value
|
46
|
+
end
|
47
|
+
def cast_binary value
|
48
|
+
value.to_s.force_encoding "BINARY"
|
49
|
+
value.gsub!(/%00|%25|\\\\/n) do |byte|
|
50
|
+
case byte
|
51
|
+
when "%00" then "\0"
|
52
|
+
when "%25" then "%"
|
53
|
+
when "\\\\" then "\\"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
def cast_boolean value
|
58
|
+
case value
|
59
|
+
when true, 1, '1', 'true', 't'
|
60
|
+
true
|
61
|
+
when false, 0, '0', 'false', 'f'
|
62
|
+
false
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'arel'
|
3
|
+
|
4
|
+
module Arel
|
5
|
+
module Visitors
|
6
|
+
VISITORS['postgres'] = VISITORS['postgresql']
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
module Arc
|
11
|
+
module DataStores
|
12
|
+
module ArelCompatibility
|
13
|
+
# tell arel how we'll be using it today
|
14
|
+
def visitor
|
15
|
+
Arel::Visitors.for self
|
16
|
+
end
|
17
|
+
def table_exists? name
|
18
|
+
!self[name.to_sym].nil?
|
19
|
+
end
|
20
|
+
# hashes, imo, have no business in a sql database
|
21
|
+
# add ability to quote a hash here to keep arel tests passing
|
22
|
+
def quote_hash h
|
23
|
+
quote_string h.to_yaml
|
24
|
+
end
|
25
|
+
# provide a method for getting at the primary key of a given table
|
26
|
+
def primary_key table
|
27
|
+
return nil unless table
|
28
|
+
table = self[table]
|
29
|
+
pk = table.column_names.find{ |c| table[c].primary_key? }
|
30
|
+
end
|
31
|
+
# the interface for each of these methods is already implemented by the existing class
|
32
|
+
# just return self
|
33
|
+
def columns_hash
|
34
|
+
self
|
35
|
+
end
|
36
|
+
def connection
|
37
|
+
self
|
38
|
+
end
|
39
|
+
def connection_pool
|
40
|
+
self
|
41
|
+
end
|
42
|
+
def spec
|
43
|
+
self
|
44
|
+
end
|
45
|
+
def with_connection
|
46
|
+
yield self
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'q/collector'
|
2
|
+
module Arc
|
3
|
+
module DataStores
|
4
|
+
module ObjectDefinitions
|
5
|
+
|
6
|
+
class Schema
|
7
|
+
include Collector
|
8
|
+
alias :table_names :keys
|
9
|
+
def initialize store
|
10
|
+
@data_store = store
|
11
|
+
end
|
12
|
+
def execute_ddl ddl
|
13
|
+
case ddl
|
14
|
+
when Array
|
15
|
+
ddl.each { |s| @data_store.execute s }
|
16
|
+
when String
|
17
|
+
execute_ddl ddl.split(';')
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class Table
|
23
|
+
include Collector
|
24
|
+
alias :column_names :keys
|
25
|
+
attr_reader :name
|
26
|
+
|
27
|
+
def initialize name, store=nil
|
28
|
+
@name, @data_store = name.to_s, store
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class Column
|
33
|
+
attr_reader :name
|
34
|
+
attr_reader :default
|
35
|
+
def initialize store, properties={}
|
36
|
+
@data_store = store
|
37
|
+
@name = properties.delete :name
|
38
|
+
@allows_null = properties.delete :allows_null
|
39
|
+
@default = properties.delete :default
|
40
|
+
@is_pk = properties.delete(:pk) || properties.delete(:primary_key)
|
41
|
+
@stype = properties.delete(:type)
|
42
|
+
end
|
43
|
+
def pk?; @is_pk; end
|
44
|
+
alias :primary_key? :pk?
|
45
|
+
def type
|
46
|
+
raise NotImplementedError
|
47
|
+
end
|
48
|
+
def allows_null?
|
49
|
+
@allows_null
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'arc/data_stores/abstract/arel_compatibility'
|
2
|
+
require 'arc/data_stores/abstract/object_definitions'
|
3
|
+
require 'arc/quoting'
|
4
|
+
|
5
|
+
module Arc
|
6
|
+
module DataStores
|
7
|
+
class AbstractDataStore < ResourcePool
|
8
|
+
include ArelCompatibility
|
9
|
+
include Arc::Quoting
|
10
|
+
|
11
|
+
def [] table
|
12
|
+
schema[table]
|
13
|
+
end
|
14
|
+
|
15
|
+
def schema
|
16
|
+
raise NotImplementedError
|
17
|
+
end
|
18
|
+
|
19
|
+
def create query
|
20
|
+
#add new data
|
21
|
+
raise NotImplementedError
|
22
|
+
end
|
23
|
+
|
24
|
+
def read query
|
25
|
+
#read existing data
|
26
|
+
raise NotImplementedError
|
27
|
+
end
|
28
|
+
|
29
|
+
def update query
|
30
|
+
#update existing data
|
31
|
+
raise NotImplementedError
|
32
|
+
end
|
33
|
+
|
34
|
+
def destroy query
|
35
|
+
#destroy existing data
|
36
|
+
raise NotImplementedError
|
37
|
+
end
|
38
|
+
|
39
|
+
def execute query
|
40
|
+
#adapters should override this method to execute a query against the database
|
41
|
+
raise NotImplementedError
|
42
|
+
end
|
43
|
+
alias :with_store :with_resource
|
44
|
+
alias :columns :[]
|
45
|
+
private
|
46
|
+
#better semantics for a class that deals with 'connections' instead of 'resources'
|
47
|
+
def create_connection
|
48
|
+
raise NotImplementedError
|
49
|
+
end
|
50
|
+
def create_resource
|
51
|
+
create_connection
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'arc/data_stores/abstract/object_definitions'
|
2
|
+
|
3
|
+
module Arc
|
4
|
+
module DataStores
|
5
|
+
module ObjectDefinitions
|
6
|
+
class MysqlSchema < Schema
|
7
|
+
def fetch_keys
|
8
|
+
@data_store.read("SHOW TABLES").map do |r|
|
9
|
+
r[r.keys.first].to_sym
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def fetch_item name
|
14
|
+
MysqlTable.new name, @data_store
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class MysqlTable < Table
|
19
|
+
def raw_column_data
|
20
|
+
@data_store.read("SHOW FULL FIELDS FROM superheros").each do |r|
|
21
|
+
r
|
22
|
+
end
|
23
|
+
end
|
24
|
+
def fetch_keys
|
25
|
+
raw_column_data.map {|c| c[:Field].to_sym }
|
26
|
+
end
|
27
|
+
def fetch_item name
|
28
|
+
return {} unless keys.include? name.to_sym
|
29
|
+
c = raw_column_data.find{|c| c[:Field] == name.to_s}
|
30
|
+
MysqlColumn.new @data_store,{
|
31
|
+
name: c[:Field],
|
32
|
+
allows_null: c[:Null] == "YES",
|
33
|
+
default: c[:Default],
|
34
|
+
primary_key: c[:Key] == "PRI",
|
35
|
+
type: c[:Type]
|
36
|
+
}
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class MysqlColumn < Column
|
41
|
+
TYPES = {
|
42
|
+
:int => :integer,
|
43
|
+
}
|
44
|
+
def type
|
45
|
+
@type ||= begin
|
46
|
+
type_key = @stype.downcase.gsub(/\([\d,]*\)/, '').to_sym
|
47
|
+
TYPES[type_key] || type_key
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'mysql2'
|
2
|
+
require 'arc/data_stores/mysql/object_definitions'
|
3
|
+
|
4
|
+
Mysql2::Client.default_query_options.merge!({
|
5
|
+
:symbolize_keys => true
|
6
|
+
})
|
7
|
+
|
8
|
+
module Arc::DataStores
|
9
|
+
class MysqlDataStore < AbstractDataStore
|
10
|
+
|
11
|
+
def read query
|
12
|
+
execute(query)
|
13
|
+
end
|
14
|
+
|
15
|
+
def create sql
|
16
|
+
table = sql.match(/\AINSERT into ([^ (]*)/i)[1]
|
17
|
+
execute sql
|
18
|
+
read("select * from #{table} where id = " + last_row_id.to_s).first
|
19
|
+
end
|
20
|
+
|
21
|
+
def update sql
|
22
|
+
execute sql
|
23
|
+
end
|
24
|
+
|
25
|
+
def destroy sql
|
26
|
+
execute sql
|
27
|
+
end
|
28
|
+
|
29
|
+
def execute query
|
30
|
+
with_store do |store|
|
31
|
+
store.query query
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def schema
|
36
|
+
@schema ||= ObjectDefinitions::MysqlSchema.new self
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
def create_connection
|
41
|
+
Mysql2::Client.new @config
|
42
|
+
end
|
43
|
+
|
44
|
+
def last_row_id
|
45
|
+
with_store do |store|
|
46
|
+
store.last_id
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|