dm-frontbase-adapter 1.1.0
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.
- 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: []
|