HanaDb 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/HanaDb.gemspec +31 -0
- data/LICENSE.txt +21 -0
- data/README.md +44 -0
- data/Rakefile +2 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/gems/activerecord-hanaclient-adapter-2.6.64.gem +0 -0
- data/gems/hanaclient-2.6.64-x86_64-linux.gem +0 -0
- data/lib/HanaDb.rb +7 -0
- data/lib/HanaDb/version.rb +3 -0
- data/lib/active_record/connection_adapters/hanaclient/column.rb +9 -0
- data/lib/active_record/connection_adapters/hanaclient/database_statements.rb +121 -0
- data/lib/active_record/connection_adapters/hanaclient/quoting.rb +48 -0
- data/lib/active_record/connection_adapters/hanaclient/schema_creation.rb +68 -0
- data/lib/active_record/connection_adapters/hanaclient/schema_definitions.rb +75 -0
- data/lib/active_record/connection_adapters/hanaclient/schema_statements.rb +287 -0
- data/lib/active_record/connection_adapters/hanaclient/transaction.rb +18 -0
- data/lib/active_record/connection_adapters/hanaclient/type_metadata.rb +29 -0
- data/lib/active_record/connection_adapters/hanaclient_adapter.rb +278 -0
- data/lib/arel/visitors/hanaclient.rb +14 -0
- metadata +97 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 4028bc5d3621954bcf5b260fe6ab3340d44142124c8ef32ce5a1e75ebbfb1ad0
|
4
|
+
data.tar.gz: 8512458d1fdb80213e810fc0974e7ceb7a714c55d3e98a51008bb0f06f4484f6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 841bba69879a219cfb788992d1035a00bc1384b59c6c7e850d7588ca1e4fd4d33d774a1977acd9ead4e4342efd6426c1910154a63605274492d0ecb0513169f8
|
7
|
+
data.tar.gz: f3ced395f25779cf8c53f3f0ecb416298322c41def387b54bf27c6b0260bbcaf6062032c021a3a453ef183e4d067b6e2bedc02083b860a12c5cd4dccedc78391
|
data/.gitignore
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
2
|
+
|
3
|
+
## Our Pledge
|
4
|
+
|
5
|
+
In the interest of fostering an open and welcoming environment, we as
|
6
|
+
contributors and maintainers pledge to making participation in our project and
|
7
|
+
our community a harassment-free experience for everyone, regardless of age, body
|
8
|
+
size, disability, ethnicity, gender identity and expression, level of experience,
|
9
|
+
nationality, personal appearance, race, religion, or sexual identity and
|
10
|
+
orientation.
|
11
|
+
|
12
|
+
## Our Standards
|
13
|
+
|
14
|
+
Examples of behavior that contributes to creating a positive environment
|
15
|
+
include:
|
16
|
+
|
17
|
+
* Using welcoming and inclusive language
|
18
|
+
* Being respectful of differing viewpoints and experiences
|
19
|
+
* Gracefully accepting constructive criticism
|
20
|
+
* Focusing on what is best for the community
|
21
|
+
* Showing empathy towards other community members
|
22
|
+
|
23
|
+
Examples of unacceptable behavior by participants include:
|
24
|
+
|
25
|
+
* The use of sexualized language or imagery and unwelcome sexual attention or
|
26
|
+
advances
|
27
|
+
* Trolling, insulting/derogatory comments, and personal or political attacks
|
28
|
+
* Public or private harassment
|
29
|
+
* Publishing others' private information, such as a physical or electronic
|
30
|
+
address, without explicit permission
|
31
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
32
|
+
professional setting
|
33
|
+
|
34
|
+
## Our Responsibilities
|
35
|
+
|
36
|
+
Project maintainers are responsible for clarifying the standards of acceptable
|
37
|
+
behavior and are expected to take appropriate and fair corrective action in
|
38
|
+
response to any instances of unacceptable behavior.
|
39
|
+
|
40
|
+
Project maintainers have the right and responsibility to remove, edit, or
|
41
|
+
reject comments, commits, code, wiki edits, issues, and other contributions
|
42
|
+
that are not aligned to this Code of Conduct, or to ban temporarily or
|
43
|
+
permanently any contributor for other behaviors that they deem inappropriate,
|
44
|
+
threatening, offensive, or harmful.
|
45
|
+
|
46
|
+
## Scope
|
47
|
+
|
48
|
+
This Code of Conduct applies both within project spaces and in public spaces
|
49
|
+
when an individual is representing the project or its community. Examples of
|
50
|
+
representing a project or community include using an official project e-mail
|
51
|
+
address, posting via an official social media account, or acting as an appointed
|
52
|
+
representative at an online or offline event. Representation of a project may be
|
53
|
+
further defined and clarified by project maintainers.
|
54
|
+
|
55
|
+
## Enforcement
|
56
|
+
|
57
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
58
|
+
reported by contacting the project team at avdhesh51000@gmail.com. All
|
59
|
+
complaints will be reviewed and investigated and will result in a response that
|
60
|
+
is deemed necessary and appropriate to the circumstances. The project team is
|
61
|
+
obligated to maintain confidentiality with regard to the reporter of an incident.
|
62
|
+
Further details of specific enforcement policies may be posted separately.
|
63
|
+
|
64
|
+
Project maintainers who do not follow or enforce the Code of Conduct in good
|
65
|
+
faith may face temporary or permanent repercussions as determined by other
|
66
|
+
members of the project's leadership.
|
67
|
+
|
68
|
+
## Attribution
|
69
|
+
|
70
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
71
|
+
available at [https://contributor-covenant.org/version/1/4][version]
|
72
|
+
|
73
|
+
[homepage]: https://contributor-covenant.org
|
74
|
+
[version]: https://contributor-covenant.org/version/1/4/
|
data/Gemfile
ADDED
data/HanaDb.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require_relative 'lib/HanaDb/version'
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = "HanaDb"
|
5
|
+
spec.version = HanaDb::VERSION
|
6
|
+
spec.authors = ["Avdhesh"]
|
7
|
+
spec.email = ["avdhesh51000@gmail.com"]
|
8
|
+
|
9
|
+
spec.summary = %q{Active record hana_adapter.}
|
10
|
+
spec.description = %q{Active record hanaclient adapter to connect with hana db.}
|
11
|
+
spec.homepage = "https://github.com/Avdhesh51000/HanaDb"
|
12
|
+
spec.license = "MIT"
|
13
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
|
14
|
+
|
15
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org/"
|
16
|
+
|
17
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
18
|
+
spec.metadata["source_code_uri"] = "https://github.com/Avdhesh51000/HanaDb"
|
19
|
+
# spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
|
20
|
+
|
21
|
+
# Specify which files should be added to the gem when it is released.
|
22
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
23
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
24
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
25
|
+
end
|
26
|
+
spec.bindir = "exe"
|
27
|
+
spec.add_development_dependency 'rails'
|
28
|
+
spec.add_runtime_dependency 'hanaclient'
|
29
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
30
|
+
spec.require_paths = ["lib"]
|
31
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2021 Avdhesh
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# HanaDb
|
2
|
+
|
3
|
+
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/HanaDb`. To experiment with that code, run `bin/console` for an interactive prompt.
|
4
|
+
|
5
|
+
TODO: Delete this and the text above, and describe your gem
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'HanaDb'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle install
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install HanaDb
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
TODO: Write usage instructions here
|
26
|
+
|
27
|
+
## Development
|
28
|
+
|
29
|
+
After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
30
|
+
|
31
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
32
|
+
|
33
|
+
## Contributing
|
34
|
+
|
35
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/HanaDb. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/HanaDb/blob/master/CODE_OF_CONDUCT.md).
|
36
|
+
|
37
|
+
|
38
|
+
## License
|
39
|
+
|
40
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
41
|
+
|
42
|
+
## Code of Conduct
|
43
|
+
|
44
|
+
Everyone interacting in the HanaDb project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/HanaDb/blob/master/CODE_OF_CONDUCT.md).
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "HanaDb"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
Binary file
|
Binary file
|
data/lib/HanaDb.rb
ADDED
@@ -0,0 +1,121 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
module Hanaclient
|
4
|
+
module DatabaseStatements
|
5
|
+
# Executes the SQL statement in the context of this connection and returns
|
6
|
+
# the raw result from the connection adapter.
|
7
|
+
def execute(sql, name = nil)
|
8
|
+
log(sql, name) do
|
9
|
+
if HA.instance.api.hanaclient_execute_immediate(@connection, sql) == 0
|
10
|
+
result, errstr = HA.instance.api.hanaclient_error(@connection)
|
11
|
+
raise ActiveRecord::StatementInvalid.new(errstr)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# Executes +sql+ statement in the context of this connection using
|
17
|
+
# +binds+ as the bind substitutes. +name+ is logged along with
|
18
|
+
# the executed +sql+ statement.
|
19
|
+
def exec_query(sql, name = "SQL", binds = [], prepare: false)
|
20
|
+
exec_and_clear(sql, name, binds, prepare: prepare) do |stmt|
|
21
|
+
record = []
|
22
|
+
columns = []
|
23
|
+
|
24
|
+
max_cols = HA.instance.api.hanaclient_num_cols(stmt)
|
25
|
+
if( max_cols > 0 )
|
26
|
+
columns = max_cols.times.collect{ |x| HA.instance.api.hanaclient_get_column_info(stmt, x)[2] }
|
27
|
+
|
28
|
+
while HA.instance.api.hanaclient_fetch_next(stmt) == 1
|
29
|
+
result = []
|
30
|
+
|
31
|
+
max_cols.times do |cols|
|
32
|
+
result << HA.instance.api.hanaclient_get_column(stmt, cols)[1]
|
33
|
+
end
|
34
|
+
|
35
|
+
record << result
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
ActiveRecord::Result.new(columns, record)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Executes the truncate statement.
|
44
|
+
def truncate(table_name, name = nil)
|
45
|
+
exec_query("TRUNCATE TABLE #{quote_table_name(table_name)}", name)
|
46
|
+
end
|
47
|
+
|
48
|
+
def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil)
|
49
|
+
result = exec_query(sql, name, binds)
|
50
|
+
unless sequence_name
|
51
|
+
table_ref = extract_table_ref_from_insert_sql(sql)
|
52
|
+
if table_ref
|
53
|
+
pk = primary_key(table_ref) if pk.nil?
|
54
|
+
pk = suppress_composite_primary_key(pk)
|
55
|
+
sequence_name = default_sequence_name(table_ref, pk)
|
56
|
+
end
|
57
|
+
return result unless sequence_name
|
58
|
+
end
|
59
|
+
last_insert_id_result(sequence_name)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Executes delete +sql+ statement in the context of this connection using
|
63
|
+
# +binds+ as the bind substitutes. +name+ is logged along with
|
64
|
+
# the executed +sql+ statement.
|
65
|
+
def exec_delete(sql, name = nil, binds = [])
|
66
|
+
exec_and_clear(sql, name, binds) do |stmt|
|
67
|
+
HA.instance.api.hanaclient_affected_rows(stmt)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
alias :exec_update :exec_delete
|
71
|
+
|
72
|
+
def reset_transaction #:nodoc:
|
73
|
+
@transaction_manager = ConnectionAdapters::Hanaclient::TransactionManager.new(self)
|
74
|
+
end
|
75
|
+
|
76
|
+
def commit_db_transaction()
|
77
|
+
HA.instance.api.hanaclient_commit(@connection)
|
78
|
+
end
|
79
|
+
|
80
|
+
def exec_rollback_db_transaction()
|
81
|
+
HA.instance.api.hanaclient_rollback(@connection)
|
82
|
+
end
|
83
|
+
|
84
|
+
def default_sequence_name(table, column)
|
85
|
+
query_value(<<-end_sql, "SCHEMA")
|
86
|
+
SELECT SEQUENCE_NAME FROM SEQUENCES WHERE SEQUENCE_NAME like ('%' || (SELECT column_id from table_columns where table_name = #{quote(table.gsub('"', ''))} AND column_name = #{quote(column.gsub('"', ''))}) || '%')
|
87
|
+
end_sql
|
88
|
+
end
|
89
|
+
|
90
|
+
def insert_fixtures(fixtures, table_name)
|
91
|
+
fixtures.each do |fixture|
|
92
|
+
insert_fixture(fixture, table_name)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def insert_fixtures_set(fixture_set, tables_to_delete = [])
|
97
|
+
disable_referential_integrity do
|
98
|
+
transaction(requires_new: true) do
|
99
|
+
tables_to_delete.each { |table| delete "DELETE FROM #{quote_table_name(table)}", "Fixture Delete" }
|
100
|
+
|
101
|
+
fixture_set.each do |table_name, rows|
|
102
|
+
rows.each { |row| insert_fixture(row, table_name) }
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# This is not supported. Can't insert an empty column
|
109
|
+
def empty_insert_statement_value
|
110
|
+
raise NotImplementedError
|
111
|
+
end
|
112
|
+
|
113
|
+
private
|
114
|
+
def suppress_composite_primary_key(pk)
|
115
|
+
pk unless pk.is_a?(Array)
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
module Hanaclient
|
4
|
+
module Quoting
|
5
|
+
|
6
|
+
# Replaces any " symbols with ""
|
7
|
+
def quote_column_name(name)
|
8
|
+
@quoted_column_names = @quoted_column_names || {}
|
9
|
+
@quoted_column_names[name] ||= %Q("#{super.gsub('"', '""')}").freeze
|
10
|
+
end
|
11
|
+
|
12
|
+
def quoted_true
|
13
|
+
"true".freeze
|
14
|
+
end
|
15
|
+
|
16
|
+
def unquoted_true
|
17
|
+
"true".freeze
|
18
|
+
end
|
19
|
+
|
20
|
+
def quoted_false
|
21
|
+
"false".freeze
|
22
|
+
end
|
23
|
+
|
24
|
+
def unquoted_false
|
25
|
+
"false".freeze
|
26
|
+
end
|
27
|
+
|
28
|
+
def quote_table_name_for_assignment(table, attr)
|
29
|
+
quote_column_name(attr)
|
30
|
+
end
|
31
|
+
|
32
|
+
def quote_table_name name
|
33
|
+
name.to_s
|
34
|
+
end
|
35
|
+
|
36
|
+
# Gets the time from a date string
|
37
|
+
def quoted_time(value)
|
38
|
+
quoted_date(value).match(/[0-2][0-9]:[0-9][0-9]:[0-9][0-9]/)[0]
|
39
|
+
end
|
40
|
+
|
41
|
+
def fetch_type_metadata(sql_type)
|
42
|
+
Hanaclient::TypeMetadata.new(super(sql_type))
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
module Hanaclient
|
4
|
+
class SchemaCreation < AbstractAdapter::SchemaCreation
|
5
|
+
private
|
6
|
+
|
7
|
+
def visit_AddColumnDefinition(o)
|
8
|
+
"ADD (#{accept(o.column)})"
|
9
|
+
end
|
10
|
+
|
11
|
+
def visit_ChangeColumnDefinition(o)
|
12
|
+
change_column_sql = "ALTER (#{accept(o.column)})"
|
13
|
+
end
|
14
|
+
|
15
|
+
def visit_TableDefinition(o)
|
16
|
+
|
17
|
+
if o.temporary && o.row_table
|
18
|
+
table_type = 'GLOBAL TEMPORARY ROW'
|
19
|
+
elsif o.temporary
|
20
|
+
table_type = 'GLOBAL TEMPORARY COLUMN'
|
21
|
+
elsif o.row_table
|
22
|
+
table_type = 'ROW'
|
23
|
+
else
|
24
|
+
table_type = 'COLUMN'
|
25
|
+
end
|
26
|
+
|
27
|
+
create_sql = "CREATE #{table_type} TABLE #{quote_table_name(o.name)} "
|
28
|
+
|
29
|
+
statements = o.columns.map { |c| accept c }
|
30
|
+
statements << accept(o.primary_keys) if o.primary_keys
|
31
|
+
|
32
|
+
if supports_indexes_in_create?
|
33
|
+
statements.concat(o.indexes.map { |column_name, options| index_in_create(o.name, column_name, options) })
|
34
|
+
end
|
35
|
+
|
36
|
+
if supports_foreign_keys_in_create?
|
37
|
+
statements.concat(o.foreign_keys.map { |to_table, options| foreign_key_in_create(o.name, to_table, options) })
|
38
|
+
end
|
39
|
+
|
40
|
+
create_sql << "(#{statements.join(', ')})" if statements.present?
|
41
|
+
add_table_options!(create_sql, table_options(o))
|
42
|
+
create_sql << " AS #{@conn.to_sql(o.as)}" if o.as
|
43
|
+
create_sql
|
44
|
+
end
|
45
|
+
|
46
|
+
def visit_PrimaryKeyDefinition(o)
|
47
|
+
"PRIMARY KEY (#{o.name.map{|name| quote_column_name(name)}.join(', ')})"
|
48
|
+
end
|
49
|
+
|
50
|
+
def add_column_options!(sql, options)
|
51
|
+
sql << " DEFAULT #{quote_default_expression(options[:default], options[:column])}" if options_include_default?(options)
|
52
|
+
# must explicitly check for :null to allow change_column to work on migrations
|
53
|
+
if options[:null] == false
|
54
|
+
sql << " NOT NULL"
|
55
|
+
end
|
56
|
+
if options[:primary_key] == true
|
57
|
+
sql << " PRIMARY KEY"
|
58
|
+
end
|
59
|
+
if options[:auto_increment] == true
|
60
|
+
sql << " GENERATED BY DEFAULT AS IDENTITY"
|
61
|
+
end
|
62
|
+
sql
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
module Hanaclient
|
4
|
+
class ReferenceDefinition < ActiveRecord::ConnectionAdapters::ReferenceDefinition
|
5
|
+
end
|
6
|
+
|
7
|
+
module ColumnMethods
|
8
|
+
def primary_key(name, type = :primary_key, **options)
|
9
|
+
options[:auto_increment] = true if [:integer, :bigint].include?(type) && !options.key?(:default)
|
10
|
+
super
|
11
|
+
end
|
12
|
+
|
13
|
+
# Generates a function for each supported database type
|
14
|
+
[
|
15
|
+
:string,
|
16
|
+
:integer,
|
17
|
+
:float,
|
18
|
+
:decimal,
|
19
|
+
:time,
|
20
|
+
:date,
|
21
|
+
:seconddate,
|
22
|
+
:timestamp,
|
23
|
+
:binary,
|
24
|
+
:unicode,
|
25
|
+
:text,
|
26
|
+
:boolean
|
27
|
+
].each do |column_type|
|
28
|
+
module_eval <<-CODE, __FILE__, __LINE__ + 1
|
29
|
+
def #{column_type}(*args, **options)
|
30
|
+
args.each { |name| column(name, :#{column_type}, options) }
|
31
|
+
end
|
32
|
+
CODE
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
|
37
|
+
include ColumnMethods
|
38
|
+
|
39
|
+
attr_accessor :indexes
|
40
|
+
attr_reader :name, :row_table, :temporary, :options, :as, :foreign_keys, :comment
|
41
|
+
|
42
|
+
def initialize(name, temporary = false, row_table = false, options = nil, as = nil, comment: nil)
|
43
|
+
@columns_hash = {}
|
44
|
+
@indexes = []
|
45
|
+
@foreign_keys = []
|
46
|
+
@primary_keys = nil
|
47
|
+
@row_table = row_table
|
48
|
+
@temporary = temporary
|
49
|
+
@options = options
|
50
|
+
@as = as
|
51
|
+
@name = name
|
52
|
+
@comment = comment
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
def new_column_definition(name, type, **options)
|
57
|
+
if type == :primary_key
|
58
|
+
type = :integer
|
59
|
+
options[:limit] ||= 8
|
60
|
+
options[:auto_increment] = true
|
61
|
+
options[:primary_key] = true
|
62
|
+
end
|
63
|
+
super
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class AlterTable < ActiveRecord::ConnectionAdapters::AlterTable
|
68
|
+
end
|
69
|
+
|
70
|
+
class Table < ActiveRecord::ConnectionAdapters::Table
|
71
|
+
include ColumnMethods
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,287 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
module Hanaclient
|
4
|
+
module SchemaStatements
|
5
|
+
|
6
|
+
def create_table(table_name, comment: nil, **options)
|
7
|
+
td = create_table_definition table_name, options[:temporary], options[:row_table], options[:options], options[:as], comment: comment
|
8
|
+
|
9
|
+
if options[:id] != false && !options[:as]
|
10
|
+
pk = options.fetch(:primary_key) do
|
11
|
+
Base.get_primary_key table_name.to_s.singularize
|
12
|
+
end
|
13
|
+
|
14
|
+
if pk.is_a?(Array)
|
15
|
+
td.primary_keys pk
|
16
|
+
else
|
17
|
+
td.primary_key pk, options.fetch(:id, :primary_key), options
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
yield td if block_given?
|
22
|
+
|
23
|
+
if options[:force]
|
24
|
+
drop_table(table_name, **options, if_exists: true)
|
25
|
+
end
|
26
|
+
|
27
|
+
result = execute schema_creation.accept td
|
28
|
+
|
29
|
+
unless supports_indexes_in_create?
|
30
|
+
td.indexes.each do |column_name, index_options|
|
31
|
+
add_index(table_name, column_name, index_options)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
if supports_comments? && !supports_comments_in_create?
|
36
|
+
change_table_comment(table_name, comment) if comment.present?
|
37
|
+
|
38
|
+
td.columns.each do |column|
|
39
|
+
change_column_comment(table_name, column.name, column.comment) if column.comment.present?
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
result
|
44
|
+
end
|
45
|
+
|
46
|
+
# Returns an array of IndexDefinition objects for the given table.
|
47
|
+
def indexes(table_name, name = nil)
|
48
|
+
if name
|
49
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
50
|
+
Passing name to #indexes is deprecated without replacement.
|
51
|
+
MSG
|
52
|
+
end
|
53
|
+
|
54
|
+
scope = quoted_scope(table_name)
|
55
|
+
|
56
|
+
sql = "SELECT TABLE_NAME, INDEX_NAME, CONSTRAINT, COLUMN_NAME FROM SYS.INDEX_COLUMNS WHERE TABLE_NAME = #{scope[:schema]} AND SCHEMA_NAME = #{scope[:name]}"
|
57
|
+
exec_and_clear(sql, "SCHEMA") do |stmt|
|
58
|
+
index_hashes = {}
|
59
|
+
while HA.instance.api.hanaclient_fetch_next(stmt) == 1
|
60
|
+
table_name = HA.instance.api.hanaclient_get_column(stmt, 0)[1]
|
61
|
+
index_name = HA.instance.api.hanaclient_get_column(stmt, 1)[1]
|
62
|
+
constraint = HA.instance.api.hanaclient_get_column(stmt, 2)[1]
|
63
|
+
column_name = HA.instance.api.hanaclient_get_column(stmt, 3)[1]
|
64
|
+
next if constraint.to_s.scan(/PRIMARY KEY/)
|
65
|
+
|
66
|
+
index_hashes[index_name] ||= IndexDefinition.new(table_name, index_name, constraint.to_s.scan(/UNIQUE/) ? true : false, [], {}, nil, nil, nil, nil, nil)
|
67
|
+
index_hashes[index_name].columns << column_name
|
68
|
+
end
|
69
|
+
|
70
|
+
index_hashes.values
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def rename_table(table_name, new_name)
|
75
|
+
execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
|
76
|
+
rename_table_indexes(table_name, new_name)
|
77
|
+
end
|
78
|
+
|
79
|
+
def drop_table(table_name, options = {})
|
80
|
+
begin
|
81
|
+
execute "DROP TABLE #{quote_table_name(table_name)} CASCADE"
|
82
|
+
rescue ActiveRecord::StatementInvalid => e
|
83
|
+
raise e unless options[:if_exists]
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def change_column(table_name, column_name, type, options = {})
|
88
|
+
column = column_for(table_name, column_name)
|
89
|
+
|
90
|
+
unless options.key?(:default)
|
91
|
+
options[:default] = column.default
|
92
|
+
end
|
93
|
+
|
94
|
+
unless options.key?(:null)
|
95
|
+
options[:null] = column.null
|
96
|
+
end
|
97
|
+
|
98
|
+
unless options.key?(:comment)
|
99
|
+
options[:comment] = column.comment
|
100
|
+
end
|
101
|
+
|
102
|
+
td = create_table_definition(table_name)
|
103
|
+
cd = td.new_column_definition(column.name, type, options)
|
104
|
+
change_column_sql = schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
|
105
|
+
|
106
|
+
execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_sql}")
|
107
|
+
end
|
108
|
+
|
109
|
+
def change_column_default(table_name, column_name, default_or_changes)
|
110
|
+
default = extract_new_default_value(default_or_changes)
|
111
|
+
column = column_for(table_name, column_name)
|
112
|
+
change_column(table_name, column_name, column.sql_type, default: default)
|
113
|
+
end
|
114
|
+
|
115
|
+
def change_column_null(table_name, column_name, null, default = nil)
|
116
|
+
column = column_for(table_name, column_name)
|
117
|
+
|
118
|
+
unless null || default.nil?
|
119
|
+
execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
|
120
|
+
end
|
121
|
+
|
122
|
+
change_column(table_name, column_name, column.sql_type, null: null)
|
123
|
+
end
|
124
|
+
|
125
|
+
def rename_column(table_name, column_name, new_column_name)
|
126
|
+
execute("RENAME COLUMN #{quote_table_name(table_name)}.#{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}")
|
127
|
+
end
|
128
|
+
|
129
|
+
def remove_index(table_name, options = {})
|
130
|
+
index_name = index_name_for_remove(table_name, options)
|
131
|
+
execute("DROP INDEX #{quote_column_name(index_name)}")
|
132
|
+
end
|
133
|
+
|
134
|
+
def rename_index(table_name, old_name, new_name)
|
135
|
+
validate_index_length!(table_name, new_name)
|
136
|
+
execute("RENAME INDEX #{quote_column_name(old_name)} TO #{quote_column_name(new_name)}")
|
137
|
+
end
|
138
|
+
|
139
|
+
# Returns an array of ForeignKeyDefinitions for a given table
|
140
|
+
def foreign_keys(table_name)
|
141
|
+
raise ArgumentError unless table_name.present?
|
142
|
+
|
143
|
+
scope = quoted_scope(table_name)
|
144
|
+
|
145
|
+
fk_info = exec_query(<<-SQL.strip_heredoc, "SCHEMA")
|
146
|
+
SELECT REFERENCED_TABLE_NAME AS "to_table",
|
147
|
+
REFERENCED_COLUMN_NAME AS "primary_key",
|
148
|
+
COLUMN_NAME AS "column",
|
149
|
+
CONSTRAINT_NAME AS "name",
|
150
|
+
UPDATE_RULE AS "on_update",
|
151
|
+
DELETE_RULE AS "on_delete"
|
152
|
+
FROM SYS.REFERENTIAL_CONSTRAINTS
|
153
|
+
WHERE SCHEMA_NAME = #{scope[:schema]}
|
154
|
+
AND TABLE_NAME = #{scope[:name]}
|
155
|
+
SQL
|
156
|
+
|
157
|
+
fk_info.map do |row|
|
158
|
+
options = {
|
159
|
+
column: row["column"],
|
160
|
+
name: row["name"],
|
161
|
+
primary_key: row["primary_key"]
|
162
|
+
}
|
163
|
+
|
164
|
+
options[:on_update] = extract_foreign_key_action(row["on_update"])
|
165
|
+
options[:on_delete] = extract_foreign_key_action(row["on_delete"])
|
166
|
+
|
167
|
+
ForeignKeyDefinition.new(table_name, row["to_table"], options)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def extract_foreign_key_action(specifier)
|
172
|
+
case specifier
|
173
|
+
when "CASCADE"; :cascade
|
174
|
+
when "SET NULL"; :nullify
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def change_table_comment(table_name, comment)
|
179
|
+
execute "COMMENT ON TABLE #{quote_table_name(table_name)} IS #{quote(comment)}"
|
180
|
+
end
|
181
|
+
|
182
|
+
def change_column_comment(table_name, column_name, comment)
|
183
|
+
execute "COMMENT ON COLUMN #{quote_table_name(table_name)}.#{quote_column_name(column_name)} IS #{quote(comment)}"
|
184
|
+
end
|
185
|
+
|
186
|
+
# Returns the sql for a given type
|
187
|
+
def type_to_sql(type, limit: nil, precision: nil, scale: nil, **) # :nodoc:
|
188
|
+
type = type.to_sym if type
|
189
|
+
if native = native_database_types[type]
|
190
|
+
column_type_sql = (native.is_a?(Hash) ? native[:name] : native).dup
|
191
|
+
|
192
|
+
if type == :integer
|
193
|
+
case limit
|
194
|
+
when 1
|
195
|
+
column_type_sql = "TINYINT"
|
196
|
+
when 2
|
197
|
+
column_type_sql = "SMALLINT"
|
198
|
+
when nil, 3, 4
|
199
|
+
column_type_sql = "INTEGER"
|
200
|
+
when 5..8
|
201
|
+
column_type_sql = "BIGINT"
|
202
|
+
else
|
203
|
+
raise(ActiveRecordError, "No integer type has byte size #{limit}.")
|
204
|
+
end
|
205
|
+
elsif type == :float
|
206
|
+
case limit
|
207
|
+
when nil, 1..24
|
208
|
+
column_type_sql = "REAL"
|
209
|
+
when 25..53
|
210
|
+
column_type_sql = "DOUBLE"
|
211
|
+
else
|
212
|
+
raise(ActiveRecordError, "No float type has byte size #{limit}.")
|
213
|
+
end
|
214
|
+
elsif type == :decimal # ignore limit, use precision and scale
|
215
|
+
scale ||= native[:scale]
|
216
|
+
|
217
|
+
if precision ||= native[:precision]
|
218
|
+
if scale
|
219
|
+
column_type_sql << "(#{precision},#{scale})"
|
220
|
+
else
|
221
|
+
column_type_sql << "(#{precision})"
|
222
|
+
end
|
223
|
+
elsif scale
|
224
|
+
raise ArgumentError, "Error adding decimal column: precision cannot be empty if scale is specified"
|
225
|
+
end
|
226
|
+
elsif type == :binary
|
227
|
+
if limit.nil? || limit.between?(1,5000)
|
228
|
+
column_type_sql = "VARBINARY(#{limit ? limit : 5000})"
|
229
|
+
else
|
230
|
+
column_type_sql = "BLOB"
|
231
|
+
end
|
232
|
+
elsif type == :unicode
|
233
|
+
if limit.nil? || limit.between?(1,5000)
|
234
|
+
column_type_sql = "NVARCHAR(#{limit ? limit : 5000})"
|
235
|
+
else
|
236
|
+
column_type_sql = "NCLOB"
|
237
|
+
end
|
238
|
+
elsif (type != :primary_key) && (limit ||= native.is_a?(Hash) && native[:limit])
|
239
|
+
column_type_sql << "(#{limit})"
|
240
|
+
end
|
241
|
+
|
242
|
+
column_type_sql.upcase
|
243
|
+
else
|
244
|
+
type.to_s.upcase
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
private
|
249
|
+
|
250
|
+
def data_source_sql(name = nil, type: nil)
|
251
|
+
scope = quoted_scope(name, type: type)
|
252
|
+
|
253
|
+
table_sql = "SELECT TABLE_NAME FROM SYS.TABLES WHERE SCHEMA_NAME = #{scope[:schema]}"
|
254
|
+
table_sql << " AND TABLE_NAME = #{scope[:name]}" if scope[:name]
|
255
|
+
|
256
|
+
view_sql = "SELECT VIEW_NAME AS TABLE_NAME FROM SYS.VIEWS WHERE SCHEMA_NAME = #{scope[:schema]}"
|
257
|
+
view_sql << " AND VIEW_NAME = #{scope[:name]}" if scope[:name]
|
258
|
+
|
259
|
+
case type
|
260
|
+
when "BASE TABLE"
|
261
|
+
return table_sql
|
262
|
+
when "VIEW"
|
263
|
+
return view_sql
|
264
|
+
end
|
265
|
+
|
266
|
+
"#{table_sql} UNION ALL #{view_sql}"
|
267
|
+
end
|
268
|
+
|
269
|
+
def quoted_scope(name = nil, type: nil)
|
270
|
+
schema, name = extract_schema_qualified_name(name)
|
271
|
+
scope = {}
|
272
|
+
scope[:schema] = schema ? quote(schema) : "CURRENT_SCHEMA"
|
273
|
+
scope[:name] = quote(name) if name
|
274
|
+
scope
|
275
|
+
end
|
276
|
+
|
277
|
+
# Extracts the schema and table name from a string of the form "schema.table"
|
278
|
+
def extract_schema_qualified_name(string)
|
279
|
+
schema, name = string.to_s.scan(/[^`.\s]+|`[^`]*`/)
|
280
|
+
schema, name = nil, schema unless name
|
281
|
+
[schema, name]
|
282
|
+
end
|
283
|
+
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
module Hanaclient
|
4
|
+
class TransactionManager < ActiveRecord::ConnectionAdapters::TransactionManager
|
5
|
+
def begin_transaction(options = {})
|
6
|
+
@connection.lock.synchronize do
|
7
|
+
run_commit_callbacks = !current_transaction.joinable?
|
8
|
+
# HANA does not support savepoints
|
9
|
+
transaction = RealTransaction.new(@connection, options, run_commit_callbacks: run_commit_callbacks)
|
10
|
+
|
11
|
+
@stack.push(transaction)
|
12
|
+
transaction
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
module Hanaclient
|
4
|
+
class TypeMetadata < DelegateClass(SqlTypeMetadata)
|
5
|
+
def initialize(type_metadata)
|
6
|
+
super(type_metadata)
|
7
|
+
@type_metadata = type_metadata
|
8
|
+
end
|
9
|
+
|
10
|
+
def ==(other)
|
11
|
+
other.is_a?(Hanaclient::TypeMetadata) &&
|
12
|
+
attributes_for_hash == other.attributes_for_hash
|
13
|
+
end
|
14
|
+
alias eql? ==
|
15
|
+
|
16
|
+
def hash
|
17
|
+
attributes_for_hash.hash
|
18
|
+
end
|
19
|
+
|
20
|
+
protected
|
21
|
+
|
22
|
+
def attributes_for_hash
|
23
|
+
[self.class, @type_metadata]
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,278 @@
|
|
1
|
+
require "singleton"
|
2
|
+
|
3
|
+
require 'arel/visitors/hanaclient'
|
4
|
+
|
5
|
+
require "active_record/connection_adapters/abstract_adapter"
|
6
|
+
require "active_record/connection_adapters/statement_pool"
|
7
|
+
|
8
|
+
require "active_record/connection_adapters/hanaclient/column"
|
9
|
+
require "active_record/connection_adapters/hanaclient/database_statements"
|
10
|
+
require "active_record/connection_adapters/hanaclient/quoting"
|
11
|
+
require "active_record/connection_adapters/hanaclient/schema_creation"
|
12
|
+
require "active_record/connection_adapters/hanaclient/schema_definitions"
|
13
|
+
require "active_record/connection_adapters/hanaclient/schema_statements"
|
14
|
+
require "active_record/connection_adapters/hanaclient/transaction"
|
15
|
+
require "active_record/connection_adapters/hanaclient/type_metadata"
|
16
|
+
|
17
|
+
require "hanaclient"
|
18
|
+
# Singleton class to hold a valid instance of the HANACLIENTInterface across all connections
|
19
|
+
class HA
|
20
|
+
include Singleton
|
21
|
+
attr_accessor :api
|
22
|
+
|
23
|
+
@@dbcapi_path = nil
|
24
|
+
def self.dbcapi_path= path
|
25
|
+
@@dbcapi_path = path
|
26
|
+
end
|
27
|
+
|
28
|
+
def initialize
|
29
|
+
@api = HANACLIENT::HANACLIENTInterface.new()
|
30
|
+
HANACLIENT::API.hanaclient_initialize_interface(@api, @@dbcapi_path)
|
31
|
+
raise LoadError, "Could not initialize HANA client library" if @api.hanaclient_init() == 0
|
32
|
+
ObjectSpace.define_finalizer(self, HA.finalize(@api))
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns a proc that is run when this object is destroyed
|
36
|
+
def self.finalize(api)
|
37
|
+
proc{
|
38
|
+
api.hanaclient_fini()
|
39
|
+
HANACLIENT::API.hanaclient_finalize_interface( api )
|
40
|
+
}
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
module ActiveRecord
|
45
|
+
module ConnectionHandling
|
46
|
+
# Establishes a connection to the database that's used by all Active Record objects
|
47
|
+
def hanaclient_connection(config)
|
48
|
+
HA.dbcapi_path = config[:dbcapi_path]
|
49
|
+
|
50
|
+
db = HA.instance.api.hanaclient_new_connection()
|
51
|
+
|
52
|
+
connection_string = "SERVERNODE=#{config[:server]}"
|
53
|
+
connection_string += ":#{config[:port]}" if config[:port]
|
54
|
+
connection_string += ";UID=#{config[:username]};PWD=#{config[:password]};"
|
55
|
+
connection_string += "DATABASENAME=#{config[:database]};" if config[:database]
|
56
|
+
# overrides the database option in connection properties if the database option is explicity given
|
57
|
+
connection_string += (config[:database] ? config[:connection_properties].gsub(/databasename=[^;]*;/i, "") : config[:connection_properties]) if config[:connection_properties]
|
58
|
+
|
59
|
+
ConnectionAdapters::HanaclientAdapter.new(db, logger, connection_string, config)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
module ConnectionAdapters
|
64
|
+
|
65
|
+
class HanaclientAdapter < AbstractAdapter
|
66
|
+
ADAPTER_NAME = "Hanaclient".freeze
|
67
|
+
|
68
|
+
include ActiveRecord::ConnectionAdapters::Hanaclient::DatabaseStatements
|
69
|
+
include ActiveRecord::ConnectionAdapters::Hanaclient::SchemaStatements
|
70
|
+
include ActiveRecord::ConnectionAdapters::Hanaclient::Quoting
|
71
|
+
|
72
|
+
# Supports all standard activerecord types and unicode
|
73
|
+
NATIVE_DATABASE_TYPES = {
|
74
|
+
primary_key: "BIGINT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY",
|
75
|
+
string: { name: "VARCHAR", limit: 3000 },
|
76
|
+
integer: { name: "INTEGER", limit: 8 },
|
77
|
+
float: { name: "FLOAT", limit: 53 },
|
78
|
+
decimal: { name: "DECIMAL" },
|
79
|
+
time: { name: "TIME" },
|
80
|
+
date: { name: "DATE" },
|
81
|
+
seconddate: { name: "SECONDDATE" },
|
82
|
+
timestamp: { name: "TIMESTAMP" },
|
83
|
+
binary: { name: "VARBINARY", limit: 3000 },
|
84
|
+
unicode: { name: "NVARCHAR", limit: 3000 },
|
85
|
+
text: { name: "CLOB" },
|
86
|
+
boolean: { name: "BOOLEAN" }
|
87
|
+
}
|
88
|
+
|
89
|
+
class StatementPool < ConnectionAdapters::StatementPool
|
90
|
+
private
|
91
|
+
|
92
|
+
def dealloc(stmt)
|
93
|
+
HA.instance.api.hanaclient_free_stmt(stmt)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def schema_creation
|
98
|
+
Hanaclient::SchemaCreation.new self
|
99
|
+
end
|
100
|
+
|
101
|
+
def arel_visitor
|
102
|
+
Arel::Visitors::Hanaclient.new(self)
|
103
|
+
end
|
104
|
+
|
105
|
+
def initialize( connection, logger, connection_string, config) #:nodoc:
|
106
|
+
super(connection, logger, config)
|
107
|
+
|
108
|
+
@statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit]))
|
109
|
+
|
110
|
+
@connection_string = connection_string
|
111
|
+
|
112
|
+
@affected_rows = 0
|
113
|
+
connect!
|
114
|
+
end
|
115
|
+
|
116
|
+
def supports_foreign_keys?
|
117
|
+
true
|
118
|
+
end
|
119
|
+
|
120
|
+
def active?
|
121
|
+
result = HA.instance.api.hanaclient_execute_immediate(@connection, "SELECT 1 FROM DUMMY") == 1
|
122
|
+
end
|
123
|
+
|
124
|
+
def connect!
|
125
|
+
result = HA.instance.api.hanaclient_connect(@connection, @connection_string)
|
126
|
+
unless result == 1
|
127
|
+
result, error = HA.instance.api.hanaclient_error(@connection)
|
128
|
+
raise ActiveRecord::StatementInvalid.new(error)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def reconnect!
|
133
|
+
super
|
134
|
+
disconnect!
|
135
|
+
connect!
|
136
|
+
end
|
137
|
+
|
138
|
+
def disconnect!
|
139
|
+
super
|
140
|
+
HA.instance.api.hanaclient_disconnect( @connection )
|
141
|
+
end
|
142
|
+
|
143
|
+
def clear_cache!
|
144
|
+
@statements.clear
|
145
|
+
end
|
146
|
+
|
147
|
+
def native_database_types
|
148
|
+
NATIVE_DATABASE_TYPES
|
149
|
+
end
|
150
|
+
|
151
|
+
# Creates a new column object given a hanaclient response statement (field)
|
152
|
+
def new_column_from_field(table_name, field)
|
153
|
+
name = field[3]
|
154
|
+
default = field[11]
|
155
|
+
sql_type_metadata = fetch_type_metadata(field[6])
|
156
|
+
nullable = field[10] == "TRUE"
|
157
|
+
table_name = field[1]
|
158
|
+
collation = field[12]
|
159
|
+
Hanaclient::Column.new(name, default, sql_type_metadata, nullable, table_name, nil, collation, comment: nil)
|
160
|
+
end
|
161
|
+
|
162
|
+
# Returns the primary keys of a given table
|
163
|
+
def primary_keys(table_name)
|
164
|
+
raise ArgumentError unless table_name.present?
|
165
|
+
|
166
|
+
scope = quoted_scope(table_name)
|
167
|
+
|
168
|
+
column_names = query_values(<<-SQL.strip_heredoc, "SCHEMA")
|
169
|
+
SELECT COLUMN_NAME
|
170
|
+
FROM SYS.INDEX_COLUMNS
|
171
|
+
WHERE CONSTRAINT = 'PRIMARY KEY'
|
172
|
+
AND SCHEMA_NAME = #{scope[:schema]}
|
173
|
+
AND TABLE_NAME = #{scope[:name]}
|
174
|
+
ORDER BY POSITION
|
175
|
+
SQL
|
176
|
+
|
177
|
+
column_names.map{|name| name}
|
178
|
+
end
|
179
|
+
|
180
|
+
private
|
181
|
+
def initialize_type_map(m)
|
182
|
+
super
|
183
|
+
m.register_type %r(date)i, Type::Date.new
|
184
|
+
m.register_type %r(time)i, Type::Time.new
|
185
|
+
m.register_type %r(seconddate)i, Type::DateTime.new
|
186
|
+
m.register_type %r(timestamp)i, Type::DateTime.new
|
187
|
+
|
188
|
+
m.register_type %r(bigint)i, Type::Integer.new(limit: 8)
|
189
|
+
m.register_type %r(integer)i, Type::Integer.new(limit: 4)
|
190
|
+
m.register_type %r(smallint)i, Type::Integer.new(limit: 2)
|
191
|
+
m.register_type %r(tinyint)i, Type::UnsignedInteger.new(limit: 1)
|
192
|
+
|
193
|
+
m.register_type %r(decimal)i, Type::Decimal.new
|
194
|
+
m.register_type %r(real)i, Type::Float.new
|
195
|
+
m.register_type %r(double)i, Type::Float.new
|
196
|
+
|
197
|
+
m.register_type %r(boolean)i, Type::Boolean.new
|
198
|
+
|
199
|
+
m.register_type %r(varchar)i, Type::String.new(limit: 5000)
|
200
|
+
m.register_type %r(nvarchar)i, Type::String.new(limit: 5000)
|
201
|
+
m.register_type %r(alphanum)i, Type::String.new(limit: 127)
|
202
|
+
m.register_type %r(varbinary)i, Type::Binary.new(limit: 5000)
|
203
|
+
|
204
|
+
m.register_type %r(blob)i, Type::Text.new(limit: 2**31 - 1)
|
205
|
+
m.register_type %r(clob)i, Type::Text.new(limit: 2**31 - 1)
|
206
|
+
m.register_type %r(nlob)i, Type::Text.new(limit: 2**31 - 1)
|
207
|
+
end
|
208
|
+
|
209
|
+
# Returns column information for a given table
|
210
|
+
def column_definitions(table_name)
|
211
|
+
scope = quoted_scope(table_name)
|
212
|
+
|
213
|
+
query(<<-end_sql, "SCHEMA")
|
214
|
+
SELECT * FROM SYS.TABLE_COLUMNS WHERE TABLE_NAME = #{scope[:name]} AND SCHEMA_NAME = #{scope[:schema]} ORDER BY POSITION
|
215
|
+
end_sql
|
216
|
+
end
|
217
|
+
|
218
|
+
def create_table_definition(*args)
|
219
|
+
Hanaclient::TableDefinition.new(*args)
|
220
|
+
end
|
221
|
+
|
222
|
+
def extract_table_ref_from_insert_sql(sql)
|
223
|
+
sql[/into\s("[A-Za-z0-9_."\[\]\s]+"|[A-Za-z0-9_."\[\]]+)\s*/im]
|
224
|
+
$1.strip if $1
|
225
|
+
end
|
226
|
+
|
227
|
+
def last_insert_id_result(sequence_name)
|
228
|
+
exec_query("SELECT #{quote_table_name(sequence_name)}.CURRVAL FROM DUMMY", "SQL")
|
229
|
+
end
|
230
|
+
|
231
|
+
# Executes an sql statement and frees the statement
|
232
|
+
def exec_and_clear(sql, name = "SQL", binds = [], prepare: false)
|
233
|
+
type_casted_binds = type_casted_binds(binds)
|
234
|
+
|
235
|
+
log(sql, name, binds, type_casted_binds) do
|
236
|
+
cached = false
|
237
|
+
|
238
|
+
# Statement caching seems to have issues. Don't use for now
|
239
|
+
# if without_prepared_statement?(binds)
|
240
|
+
stmt = HA.instance.api.hanaclient_prepare(@connection, sql)
|
241
|
+
if stmt.nil?
|
242
|
+
result, errstr = HA.instance.api.hanaclient_error(@connection)
|
243
|
+
raise ActiveRecord::StatementInvalid.new(errstr)
|
244
|
+
end
|
245
|
+
# else
|
246
|
+
# unless @statements.key? sql
|
247
|
+
# @statements[sql] = HA.instance.api.hanaclient_prepare(@connection, sql)
|
248
|
+
# if @statements[sql].nil?
|
249
|
+
# result, errstr = HA.instance.api.hanaclient_error(@connection)
|
250
|
+
# raise ActiveRecord::StatementInvalid.new(errstr)
|
251
|
+
# end
|
252
|
+
# end
|
253
|
+
# stmt = @statements[sql]
|
254
|
+
# cached = true
|
255
|
+
# end
|
256
|
+
|
257
|
+
num_params = HA.instance.api.hanaclient_num_params(stmt)
|
258
|
+
|
259
|
+
num_params.times do |i|
|
260
|
+
res, param = HA.instance.api.hanaclient_describe_bind_param(stmt, i)
|
261
|
+
param.set_value(type_casted_binds[i])
|
262
|
+
HA.instance.api.hanaclient_bind_param(stmt, i, param)
|
263
|
+
end
|
264
|
+
|
265
|
+
if HA.instance.api.hanaclient_execute(stmt) == 0
|
266
|
+
result, errstr = HA.instance.api.hanaclient_error(@connection)
|
267
|
+
raise ActiveRecord::StatementInvalid.new(errstr)
|
268
|
+
end
|
269
|
+
|
270
|
+
ret = yield stmt
|
271
|
+
HA.instance.api.hanaclient_free_stmt(stmt) unless cached
|
272
|
+
ret
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Arel
|
2
|
+
module Visitors
|
3
|
+
class Hanaclient < Arel::Visitors::ToSql
|
4
|
+
private
|
5
|
+
def visit_Arel_Nodes_As o, collector
|
6
|
+
collector = visit o.left, collector
|
7
|
+
collector << " AS "
|
8
|
+
# The alias must be quoted
|
9
|
+
o.right = Arel::Nodes::SqlLiteral.new(quote_column_name(o.right.to_s)) if o.right.instance_of?(Arel::Nodes::SqlLiteral)
|
10
|
+
visit o.right, collector
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
metadata
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: HanaDb
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Avdhesh
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-01-26 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: hanaclient
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
description: Active record hanaclient adapter to connect with hana db.
|
42
|
+
email:
|
43
|
+
- avdhesh51000@gmail.com
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- ".gitignore"
|
49
|
+
- CODE_OF_CONDUCT.md
|
50
|
+
- Gemfile
|
51
|
+
- HanaDb.gemspec
|
52
|
+
- LICENSE.txt
|
53
|
+
- README.md
|
54
|
+
- Rakefile
|
55
|
+
- bin/console
|
56
|
+
- bin/setup
|
57
|
+
- gems/activerecord-hanaclient-adapter-2.6.64.gem
|
58
|
+
- gems/hanaclient-2.6.64-x86_64-linux.gem
|
59
|
+
- lib/HanaDb.rb
|
60
|
+
- lib/HanaDb/version.rb
|
61
|
+
- lib/active_record/connection_adapters/hanaclient/column.rb
|
62
|
+
- lib/active_record/connection_adapters/hanaclient/database_statements.rb
|
63
|
+
- lib/active_record/connection_adapters/hanaclient/quoting.rb
|
64
|
+
- lib/active_record/connection_adapters/hanaclient/schema_creation.rb
|
65
|
+
- lib/active_record/connection_adapters/hanaclient/schema_definitions.rb
|
66
|
+
- lib/active_record/connection_adapters/hanaclient/schema_statements.rb
|
67
|
+
- lib/active_record/connection_adapters/hanaclient/transaction.rb
|
68
|
+
- lib/active_record/connection_adapters/hanaclient/type_metadata.rb
|
69
|
+
- lib/active_record/connection_adapters/hanaclient_adapter.rb
|
70
|
+
- lib/arel/visitors/hanaclient.rb
|
71
|
+
homepage: https://github.com/Avdhesh51000/HanaDb
|
72
|
+
licenses:
|
73
|
+
- MIT
|
74
|
+
metadata:
|
75
|
+
allowed_push_host: https://rubygems.org/
|
76
|
+
homepage_uri: https://github.com/Avdhesh51000/HanaDb
|
77
|
+
source_code_uri: https://github.com/Avdhesh51000/HanaDb
|
78
|
+
post_install_message:
|
79
|
+
rdoc_options: []
|
80
|
+
require_paths:
|
81
|
+
- lib
|
82
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
83
|
+
requirements:
|
84
|
+
- - ">="
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: 2.3.0
|
87
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '0'
|
92
|
+
requirements: []
|
93
|
+
rubygems_version: 3.1.4
|
94
|
+
signing_key:
|
95
|
+
specification_version: 4
|
96
|
+
summary: Active record hana_adapter.
|
97
|
+
test_files: []
|