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,9 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ .DS_Store
6
+ .rvmrc
7
+ tmp/
8
+ /coverage
9
+ doc/spec.html
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
@@ -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
@@ -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
@@ -0,0 +1,13 @@
1
+ require 'bundler/setup'
2
+ require 'time'
3
+
4
+ require 'q/resource_pool'
5
+ require 'q/hash'
6
+ require 'q/array'
7
+ require 'q/string'
8
+ require 'q/symbol'
9
+ require 'q/dispatcher'
10
+
11
+ require "arc/version"
12
+ require 'arc/data_stores'
13
+
@@ -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,9 @@
1
+ require 'arc/data_stores/abstract/store'
2
+
3
+ module Arc
4
+ module DataStores
5
+ extend Q::Dispatcher
6
+ require_pattern "arc/data_stores/%s/store.rb"
7
+ constant_suffix "DataStore"
8
+ end
9
+ 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