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 +5 -0
- data/Gemfile +4 -0
- data/Rakefile +1 -0
- data/dm-frontbase-adapter.gemspec +27 -0
- data/lib/dm-frontbase-adapter.rb +14 -0
- data/lib/dm-frontbase-adapter/adapter.rb +115 -0
- data/lib/dm-frontbase-adapter/connection.rb +74 -0
- data/lib/dm-frontbase-adapter/sql_query.rb +142 -0
- metadata +117 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
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: []
|