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.
Files changed (41) hide show
  1. data/.gitignore +9 -0
  2. data/.rspec +1 -0
  3. data/Gemfile +21 -0
  4. data/Rakefile +35 -0
  5. data/arc.gemspec +20 -0
  6. data/lib/arc.rb +13 -0
  7. data/lib/arc/casting.rb +66 -0
  8. data/lib/arc/data_stores.rb +9 -0
  9. data/lib/arc/data_stores/abstract/arel_compatibility.rb +50 -0
  10. data/lib/arc/data_stores/abstract/object_definitions.rb +55 -0
  11. data/lib/arc/data_stores/abstract/store.rb +55 -0
  12. data/lib/arc/data_stores/mysql/object_definitions.rb +54 -0
  13. data/lib/arc/data_stores/mysql/store.rb +51 -0
  14. data/lib/arc/data_stores/postgres/object_definitions.rb +70 -0
  15. data/lib/arc/data_stores/postgres/store.rb +38 -0
  16. data/lib/arc/data_stores/sqlite/object_definitions.rb +51 -0
  17. data/lib/arc/data_stores/sqlite/store.rb +43 -0
  18. data/lib/arc/quoting.rb +91 -0
  19. data/lib/arc/reactor.rb +29 -0
  20. data/lib/arc/version.rb +3 -0
  21. data/spec/casting_spec.rb +74 -0
  22. data/spec/data_stores/abstract/arel_compatibility_spec.rb +63 -0
  23. data/spec/data_stores/abstract/object_definitions_spec.rb +75 -0
  24. data/spec/data_stores/abstract/store_spec.rb +59 -0
  25. data/spec/data_stores/integration/crud_spec.rb +79 -0
  26. data/spec/data_stores/integration/schema_spec.rb +47 -0
  27. data/spec/data_stores/mysql/store_spec.rb +11 -0
  28. data/spec/data_stores/postgres/store_spec.rb +11 -0
  29. data/spec/data_stores/sqlite/store_spec.rb +11 -0
  30. data/spec/data_stores_spec.rb +15 -0
  31. data/spec/quoting_spec.rb +97 -0
  32. data/spec/spec_helper.rb +81 -0
  33. data/spec/support/config.yml +16 -0
  34. data/spec/support/resources/rails.png +0 -0
  35. data/spec/support/schemas/drop_mysql.sql +1 -0
  36. data/spec/support/schemas/drop_postgres.sql +1 -0
  37. data/spec/support/schemas/drop_sqlite.sql +1 -0
  38. data/spec/support/schemas/mysql.sql +10 -0
  39. data/spec/support/schemas/postgres.sql +12 -0
  40. data/spec/support/schemas/sqlite.sql +12 -0
  41. 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
@@ -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
@@ -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
+
@@ -0,0 +1,3 @@
1
+ module Arc
2
+ VERSION = "0.0.1"
3
+ end
@@ -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