dm-frontbase-adapter 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ nbproject
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in dm-frontbase-adapter.gemspec
4
+ gemspec
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,27 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "dm-frontbase-adapter"
7
+ s.version = '1.1.0'
8
+ s.authors = ["zapo"]
9
+ s.email = ["antoine.niek@supinfo.com"]
10
+ s.homepage = ""
11
+ s.summary = %q{gem summary}
12
+ s.description = %q{gem description}
13
+
14
+ s.rubyforge_project = "dm-frontbase-adapter"
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
+
21
+ # specify any dependencies here; for example:
22
+ # s.add_development_dependency "rspec"
23
+ s.add_runtime_dependency 'ruby-frontbase'
24
+ s.add_dependency "dm-core"
25
+ s.add_dependency "dm-validations"
26
+ s.add_dependency "dm-types"
27
+ end
@@ -0,0 +1,14 @@
1
+ require 'dm-core'
2
+
3
+ class FrontbaseAdapter < ::DataMapper::Adapters::AbstractAdapter
4
+ Inflector = ::DataMapper.const_defined?(:Inflector) ? ::DataMapper::Inflector : ::Extlib::Inflection
5
+ end
6
+
7
+
8
+ # add our adapter to datamapper adapter list
9
+
10
+ ::DataMapper::Adapters::FrontbaseAdapter = FrontbaseAdapter
11
+ ::DataMapper::Adapters.const_added(:FrontbaseAdapter)
12
+
13
+ require 'frontbase'
14
+ require "dm-frontbase-adapter/adapter"
@@ -0,0 +1,115 @@
1
+ require 'dm-frontbase-adapter/connection'
2
+ require 'dm-frontbase-adapter/sql_query'
3
+
4
+
5
+ class FrontbaseAdapter < ::DataMapper::Adapters::AbstractAdapter
6
+
7
+ # cache data per operation accessor
8
+ attr_accessor :data
9
+
10
+ # frontbase operations accessor
11
+ attr_accessor :models_operations
12
+
13
+ def field_naming_convention
14
+ proc {|property| property.name.to_s }
15
+ end
16
+
17
+ def resource_naming_convention
18
+ proc {|resource| resource.to_s }
19
+ end
20
+
21
+ def initialize(name, options)
22
+
23
+ # store our options in a hash :sym => value
24
+ @options = options.inject({}) {|memo, (k,v)| memo[k.to_sym] = v; memo}
25
+ @options[:encoding] ||= 'iso-8859-1'
26
+
27
+ # initialize abstract adapter
28
+ super(name, @options)
29
+ end
30
+
31
+ def log msg
32
+ DataMapper.logger.info "FrontbaseAdapter: #{msg}"
33
+ end
34
+
35
+ def connection
36
+ @connection ||= FrontbaseAdapter::Connection.new(@options)
37
+ end
38
+
39
+ def with_connection &block
40
+ block.call(connection) if block
41
+ rescue
42
+ raise $!
43
+ ensure
44
+ connection.close
45
+ end
46
+
47
+ # Simple read method that take a DataMapper::Query object that represent the query
48
+ # Returns a filtered data hash built from the query model operation returned xml
49
+ def read(query)
50
+
51
+ properties = query.fields
52
+
53
+ statement = SQLQuery.new(query, :select).to_s
54
+
55
+ log statement
56
+
57
+ records = with_connection { |connection|
58
+ response_to_a(connection.query(statement), properties)
59
+ }
60
+
61
+ query.filter_records(records)
62
+ end
63
+
64
+ def response_to_a response, props = nil
65
+ columns = response.columns
66
+ result = response.result
67
+
68
+ result.inject([]) do |arr, record|
69
+
70
+ record = Hash[*columns.zip(record).flatten].inject({}) do |hash, (column, value)|
71
+ if props && (prop = props.find {|prop| prop.field.to_sym == column.to_sym })
72
+
73
+ case
74
+ when prop.is_a?(::DataMapper::Property::Boolean)
75
+ value = case
76
+ when [1.0, 1, true, "true"].include?(value)
77
+ true
78
+ else
79
+ false
80
+ end
81
+ when prop.kind_of?(::DataMapper::Property::String)
82
+ value = value.to_s.force_encoding('ISO-8859-1').encode('UTF-8', :undef => :replace, :invalid => :replace)
83
+ end
84
+
85
+ value = prop.typecast(value)
86
+
87
+ elsif value.kind_of? String
88
+ value = value.to_s.force_encoding('ISO-8859-1').encode('UTF-8', :undef => :replace, :invalid => :replace)
89
+ end
90
+
91
+ hash[column] = value
92
+ hash
93
+ end
94
+ arr << record
95
+ arr
96
+ end
97
+ end
98
+
99
+ def describe storage_name
100
+ with_connection do |connection|
101
+ connection.describe storage_name
102
+ end
103
+ end
104
+
105
+ def show_tables
106
+ with_connection do |connection|
107
+
108
+ stmt = 'SELECT * FROM INFORMATION_SCHEMA.SCHEMATA T0, INFORMATION_SCHEMA.TABLES T1 WHERE T0."SCHEMA_PK" = T1."SCHEMA_PK";'
109
+ log stmt
110
+
111
+ records = response_to_a(connection.query(stmt))
112
+ records.find_all {|record| record[:SCHEMA_NAME] != 'INFORMATION SCHEMA' && record[:TABLE_TYPE] == 'BASE_TABLE'}.map {|record| record[:TABLE_NAME]}
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,74 @@
1
+ class FrontbaseAdapter < ::DataMapper::Adapters::AbstractAdapter
2
+
3
+ # Connection wrapper
4
+
5
+ class Connection
6
+
7
+ def initialize(options)
8
+ default = {
9
+ :host => 'localhost',
10
+ :port => -1,
11
+ :user => '',
12
+ :password => '',
13
+ :dbpassword => '',
14
+ :database => '',
15
+ }
16
+
17
+ @options = default.merge(options)
18
+ connection
19
+ end
20
+
21
+ def connection
22
+ unless @connection
23
+ @connection = FBSQL_Connect.new(
24
+ @options[:host],
25
+ @options[:port],
26
+ @options[:database],
27
+ @options[:user],
28
+ @options[:password],
29
+ @options[:dbpassword]
30
+ )
31
+ @connection.input_charset = encoding_map['UTF-8']
32
+ @connection.output_charset = encoding_map['ISO-8859-1']
33
+ end
34
+
35
+ @connection
36
+ end
37
+
38
+ def query str
39
+ connection.query str
40
+ end
41
+
42
+ def encoding_map
43
+ {
44
+ 'ISO-8859-1' => FBSQL_Connect::FBC_ISO8859_1,
45
+ 'UTF-8' => FBSQL_Connect::FBC_UTF_8
46
+ }
47
+ end
48
+
49
+ def describe(table)
50
+ sql = %[SELECT T3."COLUMN_NAME" AS NAME, T4."DATA_TYPE" FROM
51
+ INFORMATION_SCHEMA.CATALOGS T0,
52
+ INFORMATION_SCHEMA.SCHEMATA T1,
53
+ INFORMATION_SCHEMA.TABLES T2,
54
+ INFORMATION_SCHEMA.COLUMNS T3,
55
+ INFORMATION_SCHEMA.DATA_TYPE_DESCRIPTOR T4
56
+
57
+ WHERE
58
+ T0."CATALOG_PK" = T1."CATALOG_PK" AND
59
+ T1."SCHEMA_PK" = T2."SCHEMA_PK" AND
60
+ T2."TABLE_PK" = T3."TABLE_PK" AND
61
+ T3."COLUMN_PK" = T4."COLUMN_NAME_PK" AND
62
+ T1."SCHEMA_NAME" LIKE CURRENT_SCHEMA AND
63
+ T2."TABLE_NAME" LIKE '#{table}';]
64
+ connection.query(sql)
65
+ end
66
+
67
+ def close
68
+ connection.close
69
+ @connection = nil
70
+ end
71
+
72
+ end
73
+
74
+ end
@@ -0,0 +1,142 @@
1
+ class FrontbaseAdapter
2
+ class SQLQuery
3
+
4
+ include DataMapper::Query::Conditions
5
+
6
+ attr_reader :conditions, :order, :type, :from, :columns
7
+
8
+ def initialize(query, type)
9
+ @type = type
10
+ @query = query
11
+
12
+ setup_statement
13
+ end
14
+
15
+ def setup_statement
16
+
17
+ @conditions = (@query.conditions) ?
18
+ conditions_statement(@query.conditions) : ''
19
+
20
+ @order = (@query.order && !@query.order.empty?) ?
21
+ order_statement(@query.order) : ''
22
+
23
+ @columns = columns_statement @query.fields
24
+
25
+ @from = quote_name(@query.model.storage_name(@query.repository.name))
26
+ end
27
+
28
+ def order_statement orders
29
+ orders.map {|o| "#{o.target.field} #{o.operator.to_s.upcase}" }.join(', ')
30
+ end
31
+
32
+ def columns_statement properties
33
+ properties.map {|property| property_to_column_name(property) }.join(', ')
34
+ end
35
+
36
+ def to_s
37
+
38
+ statement = ''
39
+
40
+ if @type == :select
41
+ statement << "SELECT #{columns}"
42
+ statement << " FROM #{from}"
43
+ statement << " WHERE #{conditions}" unless conditions.to_s.empty?
44
+ statement << " ORDER BY #{order}" unless order.empty?
45
+ statement << ";"
46
+
47
+ if @query.limit || (@query.limit && @query.offset > 0)
48
+
49
+ replacement = "SELECT TOP("
50
+ replacement << "#{@query.offset.to_i}," if @query.limit && @query.offset > 0
51
+ replacement << "#{@query.limit.to_i}" if @query.limit
52
+ replacement << ")"
53
+
54
+ statement.gsub!('SELECT', replacement)
55
+ end
56
+ end
57
+
58
+ statement
59
+ end
60
+
61
+ def conditions_statement(conditions)
62
+ case conditions
63
+ when AbstractOperation then operation_statement(conditions)
64
+ when AbstractComparison then comparison_statement(conditions)
65
+ end
66
+ end
67
+
68
+ def operation_statement(operation)
69
+ case operation
70
+ when NotOperation then "NOT(#{conditions_statement(operation.first)})"
71
+ when AndOperation then "(#{operation.map {|op| conditions_statement(op) }.join(' AND ')})"
72
+ when OrOperation then "(#{operation.map {|op| conditions_statement(op) }.join(' OR ')})"
73
+ end
74
+ end
75
+
76
+ def comparison_statement(comparison)
77
+
78
+ return conditions_statement(comparison.foreign_key_mapping) if comparison.relationship?
79
+
80
+ value = comparison.value
81
+ subject = property_to_column_name comparison.subject
82
+
83
+ operator = case comparison
84
+ when EqualToComparison then '='
85
+ when GreaterThanComparison then '>'
86
+ when LessThanComparison then '<'
87
+ when GreaterThanOrEqualToComparison then '>='
88
+ when LessThanOrEqualToComparison then '<='
89
+ when LikeComparison then 'LIKE'
90
+ when InclusionComparison then include_operator(value)
91
+ end
92
+
93
+ "#{subject} #{operator} #{quote_value(value, comparison.subject)}"
94
+ end
95
+
96
+ def include_operator(value)
97
+ case value
98
+ when Array then 'IN'
99
+ when Range then 'BETWEEN'
100
+ end
101
+ end
102
+
103
+ def property_to_column_name(prop)
104
+ res = case prop
105
+ when DataMapper::Property
106
+ quote_name(prop.field)
107
+ when DataMapper::Query::Path
108
+ rels = prop.relationships
109
+ names = rels.map {|r| storage_name(r, @query.repository) }.join(".")
110
+ "#{names}.#{quote_name(prop.field)}"
111
+ end
112
+
113
+ res
114
+ end
115
+
116
+ def quote_name(name)
117
+ "\"#{name.gsub('"', '""')}\""
118
+ end
119
+
120
+ def storage_name(rel, repository)
121
+ rel.parent_model.storage_name(repository.name)
122
+ end
123
+
124
+
125
+ def quote_value(value, property)
126
+ if property.kind_of? DataMapper::Property::Boolean
127
+ return value == DataMapper::Property::Boolean::TRUE ? 'TRUE' : 'FALSE'
128
+ end
129
+
130
+ case
131
+ when value.kind_of?(Array)
132
+ "(#{value.map {|v| quote_value(v, property)}.join(", ")})"
133
+ when value.kind_of?(NilClass)
134
+ "NULL"
135
+ when value.kind_of?(String)
136
+ "'#{value.to_s.gsub(/'/, "\\'").gsub(/\\/, %{\\\\})}'"
137
+ else
138
+ value.to_s
139
+ end
140
+ end
141
+ end
142
+ end
metadata ADDED
@@ -0,0 +1,117 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dm-frontbase-adapter
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - zapo
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-05-09 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: ruby-frontbase
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: dm-core
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: dm-validations
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: dm-types
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ description: gem description
79
+ email:
80
+ - antoine.niek@supinfo.com
81
+ executables: []
82
+ extensions: []
83
+ extra_rdoc_files: []
84
+ files:
85
+ - .gitignore
86
+ - Gemfile
87
+ - Rakefile
88
+ - dm-frontbase-adapter.gemspec
89
+ - lib/dm-frontbase-adapter.rb
90
+ - lib/dm-frontbase-adapter/adapter.rb
91
+ - lib/dm-frontbase-adapter/connection.rb
92
+ - lib/dm-frontbase-adapter/sql_query.rb
93
+ homepage: ''
94
+ licenses: []
95
+ post_install_message:
96
+ rdoc_options: []
97
+ require_paths:
98
+ - lib
99
+ required_ruby_version: !ruby/object:Gem::Requirement
100
+ none: false
101
+ requirements:
102
+ - - ! '>='
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ required_rubygems_version: !ruby/object:Gem::Requirement
106
+ none: false
107
+ requirements:
108
+ - - ! '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ requirements: []
112
+ rubyforge_project: dm-frontbase-adapter
113
+ rubygems_version: 1.8.23
114
+ signing_key:
115
+ specification_version: 3
116
+ summary: gem summary
117
+ test_files: []