arc 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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