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
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'arc/data_stores/abstract/object_definitions'
|
2
|
+
|
3
|
+
module Arc
|
4
|
+
module DataStores
|
5
|
+
module ObjectDefinitions
|
6
|
+
|
7
|
+
class PostgresSchema < Schema
|
8
|
+
def fetch_keys
|
9
|
+
names = @data_store.read("select tablename from pg_tables where schemaname = ANY(current_schemas(false))")
|
10
|
+
names = names.to_a.map { |n| n[:tablename].to_sym }
|
11
|
+
end
|
12
|
+
def fetch_item name
|
13
|
+
PostgresTable.new name, @data_store
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class PostgresTable < Table
|
18
|
+
def raw_column_data
|
19
|
+
column_info_query = <<-SQL
|
20
|
+
SELECT col.attrelid::regclass as table,
|
21
|
+
col.attname as name,
|
22
|
+
format_type(col.atttypid, col.atttypmod) as type,
|
23
|
+
col.attnotnull as not_null,
|
24
|
+
def.adsrc as default,
|
25
|
+
( SELECT d.refobjid
|
26
|
+
FROM pg_depend d
|
27
|
+
WHERE col.attrelid = d.refobjid
|
28
|
+
AND col.attnum = d.refobjsubid
|
29
|
+
LIMIT 1
|
30
|
+
) IS NOT NULL as primary_key
|
31
|
+
FROM pg_attribute as col
|
32
|
+
LEFT JOIN pg_attrdef def
|
33
|
+
ON col.attrelid = def.adrelid
|
34
|
+
AND col.attnum = def.adnum
|
35
|
+
JOIN pg_tables tbl
|
36
|
+
ON col.attrelid::regclass = tbl.tablename::regclass
|
37
|
+
WHERE tbl.schemaname = ANY (current_schemas(false))
|
38
|
+
AND col.attnum > 0
|
39
|
+
AND tbl.tablename = '#{name}'
|
40
|
+
ORDER BY primary_key DESC
|
41
|
+
SQL
|
42
|
+
@data_store.read(column_info_query).to_a.symbolize_keys!
|
43
|
+
end
|
44
|
+
def fetch_keys
|
45
|
+
raw_column_data.map {|c| c[:name].to_sym }
|
46
|
+
end
|
47
|
+
def fetch_item name
|
48
|
+
return {} unless keys.include? name.to_sym
|
49
|
+
c = raw_column_data.find{|c| c[:name] == name.to_s}
|
50
|
+
PostgresColumn.new @data_store,{
|
51
|
+
name: c[:name],
|
52
|
+
allows_null: c[:not_null] == "f",
|
53
|
+
default: nil,
|
54
|
+
primary_key: c[:primary_key] == "t",
|
55
|
+
type: c[:type]
|
56
|
+
}
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class PostgresColumn < Column
|
61
|
+
def type
|
62
|
+
return :varchar if @stype =~ /character varying/
|
63
|
+
return :time if @stype =~ /timestamp/
|
64
|
+
@stype.to_sym
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'pg'
|
2
|
+
require 'arc/data_stores/postgres/object_definitions'
|
3
|
+
|
4
|
+
module Arc
|
5
|
+
module DataStores
|
6
|
+
class PostgresDataStore < AbstractDataStore
|
7
|
+
def read query
|
8
|
+
execute(query).to_a.symbolize_keys!
|
9
|
+
end
|
10
|
+
def create sql
|
11
|
+
table = sql.match(/\AINSERT into ([^ (]*)/i)[1]
|
12
|
+
sql[-1] = sql[-1] == ';' ? '' : sql[-1]
|
13
|
+
sql += " RETURNING id" unless sql =~ /returning/
|
14
|
+
id = execute(sql).to_a[0].first[1]
|
15
|
+
read("select * from #{table} where id = #{id}")[0]
|
16
|
+
end
|
17
|
+
def execute query
|
18
|
+
with_store { |connection| connection.exec query }
|
19
|
+
end
|
20
|
+
alias :destroy :execute
|
21
|
+
alias :update :execute
|
22
|
+
|
23
|
+
def schema
|
24
|
+
@schema ||= ObjectDefinitions::PostgresSchema.new self
|
25
|
+
end
|
26
|
+
private
|
27
|
+
def create_connection
|
28
|
+
PGconn.connect({
|
29
|
+
:dbname => @config[:database],
|
30
|
+
:user => @config[:user],
|
31
|
+
:password => @config[:password],
|
32
|
+
:host => @config[:host],
|
33
|
+
:port => @config[:port]
|
34
|
+
})
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'arc/data_stores/abstract/object_definitions'
|
2
|
+
|
3
|
+
module Arc
|
4
|
+
module DataStores
|
5
|
+
module ObjectDefinitions
|
6
|
+
class SqliteSchema < Schema
|
7
|
+
def fetch_keys
|
8
|
+
r = @data_store.read <<-SQL
|
9
|
+
SELECT name FROM sqlite_master
|
10
|
+
WHERE type='table' AND name != 'sqlite_sequence';
|
11
|
+
SQL
|
12
|
+
r.map{ |t| t[:name].to_sym }
|
13
|
+
end
|
14
|
+
|
15
|
+
def fetch_item name
|
16
|
+
SqliteTable.new name, @data_store
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class SqliteTable < Table
|
21
|
+
def raw_column_data
|
22
|
+
@rcd ||= @data_store.read <<-SQL
|
23
|
+
PRAGMA table_info('#{@name}');
|
24
|
+
SQL
|
25
|
+
end
|
26
|
+
def fetch_keys
|
27
|
+
raw_column_data.map { |c| c[:name].to_sym }
|
28
|
+
end
|
29
|
+
def fetch_item column_name
|
30
|
+
c = raw_column_data.select {|c| c[:name] == column_name.to_s }.first || {}
|
31
|
+
c = {
|
32
|
+
name: c[:name],
|
33
|
+
allows_null: c[:notnull] == 0,
|
34
|
+
default: c[:dflt_value],
|
35
|
+
primary_key: c[:pk] == 1,
|
36
|
+
type: c[:type]
|
37
|
+
}
|
38
|
+
SqliteColumn.new @data_store, c
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class SqliteColumn < Column
|
43
|
+
def type
|
44
|
+
#simplify the type expression to remove length and precision
|
45
|
+
@type ||= @stype.downcase.gsub(/\([\d,]*\)/, '').to_sym
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'sqlite3'
|
2
|
+
require 'arc/data_stores/sqlite/object_definitions'
|
3
|
+
|
4
|
+
module Arc
|
5
|
+
module DataStores
|
6
|
+
class SqliteDataStore < AbstractDataStore
|
7
|
+
|
8
|
+
def read query
|
9
|
+
execute(query).symbolize_keys!
|
10
|
+
end
|
11
|
+
|
12
|
+
def create sql
|
13
|
+
table = sql.match(/\AINSERT into ([^ (]*)/i)[1]
|
14
|
+
execute sql
|
15
|
+
read("select * from #{table} where id = last_insert_rowid();")[0]
|
16
|
+
end
|
17
|
+
|
18
|
+
def update sql
|
19
|
+
execute sql
|
20
|
+
end
|
21
|
+
|
22
|
+
def destroy sql
|
23
|
+
execute sql
|
24
|
+
end
|
25
|
+
|
26
|
+
def execute query
|
27
|
+
with_store do |connection|
|
28
|
+
result = connection.execute(query)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def schema
|
33
|
+
@schema ||= ObjectDefinitions::SqliteSchema.new self
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
def create_connection
|
38
|
+
SQLite3::Database.new @config[:database], :results_as_hash => true
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/arc/quoting.rb
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'date'
|
2
|
+
require 'bigdecimal'
|
3
|
+
|
4
|
+
module Arc
|
5
|
+
module Quoting
|
6
|
+
class CannotCastValueError < StandardError; end
|
7
|
+
# Quotes the column name. Defaults to no quoting.
|
8
|
+
def quote_column(column_name)
|
9
|
+
"\"#{column_name}\""
|
10
|
+
end
|
11
|
+
alias :quote_column_name :quote_column
|
12
|
+
|
13
|
+
# Quotes the table name. Defaults to column name quoting.
|
14
|
+
def quote_table(table_name)
|
15
|
+
quote_column_name(table_name)
|
16
|
+
end
|
17
|
+
alias :quote_table_name :quote_table
|
18
|
+
|
19
|
+
def quote(value, klass=value.class)
|
20
|
+
return quote_nil if (value.nil?)
|
21
|
+
klass = value.class if klass.nil?
|
22
|
+
if value.is_a? Class
|
23
|
+
return quote_string value.name
|
24
|
+
end
|
25
|
+
case klass
|
26
|
+
when String, Symbol
|
27
|
+
method = "quote_#{klass.to_s}"
|
28
|
+
return send method, value if respond_to? method, :include_private => true
|
29
|
+
when Arc::DataStores::ObjectDefinitions::Column
|
30
|
+
method = "quote_#{Arc::Casting::TYPES[klass.type.to_sym]}"
|
31
|
+
return send(method, value)
|
32
|
+
else
|
33
|
+
while klass.name do
|
34
|
+
method = "quote_#{klass.name.downcase.gsub(/class/,'')}"
|
35
|
+
return send method, value if respond_to? method, :include_private => true
|
36
|
+
klass = klass.superclass
|
37
|
+
end
|
38
|
+
end
|
39
|
+
raise CannotCastValueError, "Unable to cast #{value}"
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
# Quotes a string, escaping any ' (single quote) and \ (backslash)
|
44
|
+
# characters.
|
45
|
+
def quote_symbol s
|
46
|
+
quote_string s.to_s
|
47
|
+
end
|
48
|
+
def quote_string(s)
|
49
|
+
"'#{s.gsub(/\\/, '\&\&').gsub(/'/, "''")}'"
|
50
|
+
end
|
51
|
+
def quote_true value=true
|
52
|
+
quote_string 't'
|
53
|
+
end
|
54
|
+
def quote_boolean value
|
55
|
+
value ? quote_true : quote_false
|
56
|
+
end
|
57
|
+
def quote_false value=false
|
58
|
+
quote_string 'f'
|
59
|
+
end
|
60
|
+
def quote_date(value)
|
61
|
+
return quote_string value if value.is_a? String
|
62
|
+
quote_string "#{value.strftime '%Y-%m-%d'}"
|
63
|
+
end
|
64
|
+
def quote_time value
|
65
|
+
return quote_string value if value.is_a? String
|
66
|
+
quote_string "#{value.strftime '%Y-%m-%d %H:%M:%S'}"
|
67
|
+
end
|
68
|
+
def quote_numeric value
|
69
|
+
value.to_s
|
70
|
+
end
|
71
|
+
def quote_integer value
|
72
|
+
quote_numeric value.to_i
|
73
|
+
end
|
74
|
+
def quote_bigdecimal value
|
75
|
+
return value if value.is_a? String
|
76
|
+
value.to_s 'F'
|
77
|
+
end
|
78
|
+
def quote_nil(value=nil)
|
79
|
+
'NULL'
|
80
|
+
end
|
81
|
+
def quote_binary value
|
82
|
+
value.gsub!(/\0|\%/n) do |byte|
|
83
|
+
case byte
|
84
|
+
when "\0" then "%00"
|
85
|
+
when '%' then "%25"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
quote_string value
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
data/lib/arc/reactor.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
module Arc
|
2
|
+
module Reactor
|
3
|
+
|
4
|
+
def self.included(klass)
|
5
|
+
klass.extend(ClassMethods)
|
6
|
+
end
|
7
|
+
|
8
|
+
def data_store
|
9
|
+
self.class.data_store
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
def connect(config)
|
14
|
+
@data_store = Arc::DataStores[config[:adapter]].new config
|
15
|
+
end
|
16
|
+
def data_store
|
17
|
+
@data_store
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
#Example Use:
|
25
|
+
# class MyReactorBase
|
26
|
+
# include Arc::Reactor
|
27
|
+
# connect("hello world")
|
28
|
+
# end
|
29
|
+
|
data/lib/arc/version.rb
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'arc/casting'
|
3
|
+
|
4
|
+
module Arc
|
5
|
+
describe Casting do
|
6
|
+
before :all do
|
7
|
+
@caster = Class.new { include Casting }.new
|
8
|
+
end
|
9
|
+
describe '#cast' do
|
10
|
+
|
11
|
+
it 'casts a string to an integer' do
|
12
|
+
value = @caster.cast "1", :integer
|
13
|
+
value.should == 1
|
14
|
+
value = @caster.cast "1", :int
|
15
|
+
value.should == 1
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'casts a string to a float' do
|
19
|
+
value = @caster.cast "1.1", :float
|
20
|
+
value.should == 1.1
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'casts a string to a time' do
|
24
|
+
time = Time.now.round(0)
|
25
|
+
value = @caster.cast time.to_s, :time
|
26
|
+
value.should == time
|
27
|
+
value = @caster.cast time.to_s, :datetime
|
28
|
+
value.should == time
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'casts a string to a date' do
|
32
|
+
date = Date.today
|
33
|
+
value = @caster.cast date.to_s, :date
|
34
|
+
value.should == date
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'unescapes binary data' do
|
38
|
+
binary = "%25%00\\\\"
|
39
|
+
value = @caster.cast binary, :binary
|
40
|
+
value.should == "%\0\\"
|
41
|
+
end
|
42
|
+
|
43
|
+
it "casts 'true'" do
|
44
|
+
truths = ['true', true, 't', '1', 1]
|
45
|
+
truths.each do |truth|
|
46
|
+
value = @caster.cast truth, :bool
|
47
|
+
value.should be_true
|
48
|
+
value = @caster.cast truth, :boolean
|
49
|
+
value.should be_true
|
50
|
+
value = @caster.cast true, :bit
|
51
|
+
value.should be_true
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
it "casts 'false'" do
|
56
|
+
lies = ['false', false, 'f', '0', 0]
|
57
|
+
lies.each do |lie|
|
58
|
+
value = @caster.cast lie, :bool
|
59
|
+
value.should be_false
|
60
|
+
value = @caster.cast lie, :boolean
|
61
|
+
value.should be_false
|
62
|
+
value = @caster.cast lie, :bit
|
63
|
+
value.should be_false
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'defaults to string if no casting method is found' do
|
68
|
+
value = @caster.cast 'superman', :awesome
|
69
|
+
value.should == 'superman'
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Arc
|
4
|
+
module DataStores
|
5
|
+
describe ArelCompatibility do
|
6
|
+
it '#specifies which arel visitor to use' do
|
7
|
+
ArcTest.with_store do |s|
|
8
|
+
Arel::Visitors.for s
|
9
|
+
end
|
10
|
+
end
|
11
|
+
it '#responds to #connection_pool#spec#config' do
|
12
|
+
ArcTest.with_store do |s|
|
13
|
+
s.connection_pool.spec.config[:adapter].should == ArcTest.config_key.to_s
|
14
|
+
end
|
15
|
+
end
|
16
|
+
it 'responds to #with_connection and yields an object which has an execute method' do
|
17
|
+
ArcTest.with_store do |s|
|
18
|
+
s.with_connection do |connection|
|
19
|
+
s.connection.should respond_to(:execute)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
it 'provides a visitor at #connection.visitor' do
|
24
|
+
ArcTest.with_store do |s|
|
25
|
+
s.stub!(:visitor).and_return("lol")
|
26
|
+
s.connection.visitor.should == "lol"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
it "aliases #quote_table to #quote_table_name" do
|
30
|
+
ArcTest.with_store do |s|
|
31
|
+
s.method(:quote_table).should == s.method(:quote_table_name)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
it 'aliases quote_column to #quote_column_name' do
|
35
|
+
ArcTest.with_store do |s|
|
36
|
+
s.method(:quote_column).should == s.method(:quote_column_name)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
it 'provides a table_exists? method' do
|
40
|
+
ArcTest.with_store do |s|
|
41
|
+
s.table_exists?(:superheros).should be_true
|
42
|
+
end
|
43
|
+
end
|
44
|
+
it 'provides a hashlike object at #connection_pool.columns_hash' do
|
45
|
+
ArcTest.with_store do |s|
|
46
|
+
s.connection_pool.columns_hash.should be(s)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
it 'responds to primary_key given a table name' do
|
50
|
+
ArcTest.with_store do |s|
|
51
|
+
s.connection.primary_key(:superheros).should be(:id)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
it 'should quote a hash' do
|
55
|
+
ArcTest.with_store do |s|
|
56
|
+
hash = {:a => :b}
|
57
|
+
s.quote_hash(hash).should == "'#{hash.to_yaml}'"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|