fluiddb2 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7d3233bc931c69ea807e944dd613485b0f10212c
4
+ data.tar.gz: 3c14618c2e985e2a1e419ebffa298dd05b6afccd
5
+ SHA512:
6
+ metadata.gz: 96a412a1de13c85d00e0fe36ed6bcb144fff9c9a38bb8a48f7e45592793bb1da83b1f104dea45d4c743bebb2448c4cd4c3cef4ad468046cd4a83c6c03cb01dde
7
+ data.tar.gz: 3204ac238c68f9a31121de945ae4f5dd3070b0f10732b9deb741623e224c3f5062cbfd64e6cbe62be0be30233600590ac0fde34446275a507380c72073bad799
data/LICENSE ADDED
@@ -0,0 +1,165 @@
1
+ GNU LESSER GENERAL PUBLIC LICENSE
2
+ Version 3, 29 June 2007
3
+
4
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
5
+ Everyone is permitted to copy and distribute verbatim copies
6
+ of this license document, but changing it is not allowed.
7
+
8
+
9
+ This version of the GNU Lesser General Public License incorporates
10
+ the terms and conditions of version 3 of the GNU General Public
11
+ License, supplemented by the additional permissions listed below.
12
+
13
+ 0. Additional Definitions.
14
+
15
+ As used herein, "this License" refers to version 3 of the GNU Lesser
16
+ General Public License, and the "GNU GPL" refers to version 3 of the GNU
17
+ General Public License.
18
+
19
+ "The Library" refers to a covered work governed by this License,
20
+ other than an Application or a Combined Work as defined below.
21
+
22
+ An "Application" is any work that makes use of an interface provided
23
+ by the Library, but which is not otherwise based on the Library.
24
+ Defining a subclass of a class defined by the Library is deemed a mode
25
+ of using an interface provided by the Library.
26
+
27
+ A "Combined Work" is a work produced by combining or linking an
28
+ Application with the Library. The particular version of the Library
29
+ with which the Combined Work was made is also called the "Linked
30
+ Version".
31
+
32
+ The "Minimal Corresponding Source" for a Combined Work means the
33
+ Corresponding Source for the Combined Work, excluding any source code
34
+ for portions of the Combined Work that, considered in isolation, are
35
+ based on the Application, and not on the Linked Version.
36
+
37
+ The "Corresponding Application Code" for a Combined Work means the
38
+ object code and/or source code for the Application, including any data
39
+ and utility programs needed for reproducing the Combined Work from the
40
+ Application, but excluding the System Libraries of the Combined Work.
41
+
42
+ 1. Exception to Section 3 of the GNU GPL.
43
+
44
+ You may convey a covered work under sections 3 and 4 of this License
45
+ without being bound by section 3 of the GNU GPL.
46
+
47
+ 2. Conveying Modified Versions.
48
+
49
+ If you modify a copy of the Library, and, in your modifications, a
50
+ facility refers to a function or data to be supplied by an Application
51
+ that uses the facility (other than as an argument passed when the
52
+ facility is invoked), then you may convey a copy of the modified
53
+ version:
54
+
55
+ a) under this License, provided that you make a good faith effort to
56
+ ensure that, in the event an Application does not supply the
57
+ function or data, the facility still operates, and performs
58
+ whatever part of its purpose remains meaningful, or
59
+
60
+ b) under the GNU GPL, with none of the additional permissions of
61
+ this License applicable to that copy.
62
+
63
+ 3. Object Code Incorporating Material from Library Header Files.
64
+
65
+ The object code form of an Application may incorporate material from
66
+ a header file that is part of the Library. You may convey such object
67
+ code under terms of your choice, provided that, if the incorporated
68
+ material is not limited to numerical parameters, data structure
69
+ layouts and accessors, or small macros, inline functions and templates
70
+ (ten or fewer lines in length), you do both of the following:
71
+
72
+ a) Give prominent notice with each copy of the object code that the
73
+ Library is used in it and that the Library and its use are
74
+ covered by this License.
75
+
76
+ b) Accompany the object code with a copy of the GNU GPL and this license
77
+ document.
78
+
79
+ 4. Combined Works.
80
+
81
+ You may convey a Combined Work under terms of your choice that,
82
+ taken together, effectively do not restrict modification of the
83
+ portions of the Library contained in the Combined Work and reverse
84
+ engineering for debugging such modifications, if you also do each of
85
+ the following:
86
+
87
+ a) Give prominent notice with each copy of the Combined Work that
88
+ the Library is used in it and that the Library and its use are
89
+ covered by this License.
90
+
91
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
92
+ document.
93
+
94
+ c) For a Combined Work that displays copyright notices during
95
+ execution, include the copyright notice for the Library among
96
+ these notices, as well as a reference directing the user to the
97
+ copies of the GNU GPL and this license document.
98
+
99
+ d) Do one of the following:
100
+
101
+ 0) Convey the Minimal Corresponding Source under the terms of this
102
+ License, and the Corresponding Application Code in a form
103
+ suitable for, and under terms that permit, the user to
104
+ recombine or relink the Application with a modified version of
105
+ the Linked Version to produce a modified Combined Work, in the
106
+ manner specified by section 6 of the GNU GPL for conveying
107
+ Corresponding Source.
108
+
109
+ 1) Use a suitable shared library mechanism for linking with the
110
+ Library. A suitable mechanism is one that (a) uses at run time
111
+ a copy of the Library already present on the user's computer
112
+ system, and (b) will operate properly with a modified version
113
+ of the Library that is interface-compatible with the Linked
114
+ Version.
115
+
116
+ e) Provide Installation Information, but only if you would otherwise
117
+ be required to provide such information under section 6 of the
118
+ GNU GPL, and only to the extent that such information is
119
+ necessary to install and execute a modified version of the
120
+ Combined Work produced by recombining or relinking the
121
+ Application with a modified version of the Linked Version. (If
122
+ you use option 4d0, the Installation Information must accompany
123
+ the Minimal Corresponding Source and Corresponding Application
124
+ Code. If you use option 4d1, you must provide the Installation
125
+ Information in the manner specified by section 6 of the GNU GPL
126
+ for conveying Corresponding Source.)
127
+
128
+ 5. Combined Libraries.
129
+
130
+ You may place library facilities that are a work based on the
131
+ Library side by side in a single library together with other library
132
+ facilities that are not Applications and are not covered by this
133
+ License, and convey such a combined library under terms of your
134
+ choice, if you do both of the following:
135
+
136
+ a) Accompany the combined library with a copy of the same work based
137
+ on the Library, uncombined with any other library facilities,
138
+ conveyed under the terms of this License.
139
+
140
+ b) Give prominent notice with the combined library that part of it
141
+ is a work based on the Library, and explaining where to find the
142
+ accompanying uncombined form of the same work.
143
+
144
+ 6. Revised Versions of the GNU Lesser General Public License.
145
+
146
+ The Free Software Foundation may publish revised and/or new versions
147
+ of the GNU Lesser General Public License from time to time. Such new
148
+ versions will be similar in spirit to the present version, but may
149
+ differ in detail to address new problems or concerns.
150
+
151
+ Each version is given a distinguishing version number. If the
152
+ Library as you received it specifies that a certain numbered version
153
+ of the GNU Lesser General Public License "or any later version"
154
+ applies to it, you have the option of following the terms and
155
+ conditions either of that published version or of any later version
156
+ published by the Free Software Foundation. If the Library as you
157
+ received it does not specify a version number of the GNU Lesser
158
+ General Public License, you may choose any version of the GNU Lesser
159
+ General Public License ever published by the Free Software Foundation.
160
+
161
+ If the Library as you received it specifies that a proxy can decide
162
+ whether future versions of the GNU Lesser General Public License shall
163
+ apply, that proxy's public statement of acceptance of any version is
164
+ permanent authorization for you to choose that version for the
165
+ Library.
data/README.md ADDED
@@ -0,0 +1,10 @@
1
+ #Fluid DB
2
+
3
+ A semantic layer for db interaction
4
+
5
+ ##install
6
+ gem install fluiddb2
7
+
8
+ ##Examples
9
+
10
+ See the tests for a reasonable run down on usage.
@@ -0,0 +1,126 @@
1
+ require 'FluidDb2'
2
+ require 'fb'
3
+ include Fb
4
+
5
+ module FluidDb2
6
+ # Firebird
7
+ class Firebird < Base
8
+ # Connect to Db.
9
+ #
10
+ # @param [String] uri a location for the resource to which we will attach,
11
+ # eg mysql://user:pass@127.0.0.1/foo
12
+ def connect
13
+ uri = @uri
14
+
15
+ user = uri.user || 'sysdba'
16
+ password = uri.password || 'masterkey'
17
+ port = uri.port || 3050
18
+
19
+ path = uri.path
20
+ path = path.slice(1, uri.path.length - 1) if path.slice(0, 3) == '/C:'
21
+ path = URI.unescape(path)
22
+
23
+ # The Database class acts as a factory for Connections.
24
+ # It can also create and drop databases.
25
+ db = Database.new(:database => "#{uri.host}/#{port}:#{path}",
26
+ :username => user,
27
+ :password => password)
28
+ # :database is the only parameter without a default.
29
+ # Let's connect to the database, creating it if it doesn't already exist.
30
+ @connection = db.connect rescue db.create.connect
31
+ end
32
+
33
+ def close
34
+ @connection.close
35
+ end
36
+
37
+ def query_for_array(sql, params = [])
38
+ sql = format_to_sql(sql, params)
39
+ list = @connection.query(:hash, sql)
40
+
41
+ case list.length
42
+ when -1
43
+ fail FluidDb2::ConnectionError
44
+ when 0
45
+ fail FluidDb2::NoDataFoundError
46
+ when 1
47
+ return list[0]
48
+ else
49
+ fail FluidDb2::TooManyRowsError
50
+ end
51
+ end
52
+
53
+ def query_for_value(sql, params = [])
54
+ sql = format_to_sql(sql, params)
55
+ results = @connection.query(sql)
56
+
57
+ case results.length
58
+ when -1
59
+ fail FluidDb::ConnectionError
60
+ when 0
61
+ fail FluidDb::NoDataFoundError
62
+ when 1
63
+ return results[0][0]
64
+ else
65
+ fail FluidDb::TooManyRowsError
66
+ end
67
+ end
68
+
69
+ def query_for_resultset(sql, params = [])
70
+ sql = format_to_sql(sql, params)
71
+ list = @connection.query(:hash, sql)
72
+
73
+ case list.length
74
+ when -1
75
+ fail FluidDb::ConnectionError
76
+ else
77
+ return list
78
+ end
79
+ end
80
+
81
+ def execute(sql, params = [], expected_affected_rows = nil)
82
+ sql = format_to_sql(sql, params)
83
+ verbose_log "#{self.class.name}.execute. #{sql}"
84
+ affected_rows = @connection.execute(sql)
85
+
86
+ if !expected_affected_rows.nil? && affected_rows != expected_affected_rows
87
+ fail ExpectedAffectedRowsError,
88
+ "Expected affected rows, #{expected_affected_rows}, Actual affected rows, #{affected_rows}"
89
+ end
90
+ end
91
+
92
+ def exec_params(sql, params, expected_affected_rows = nil)
93
+ parts = sql.split('?')
94
+ sql = ''
95
+ parts.each_with_index do |p, idx|
96
+ sql += p
97
+ sql += "$#{idx + 1}" if idx < parts.length - 1
98
+ end
99
+ affected_rows = @connection.exec_params(sql, params)
100
+
101
+ if !expected_affected_rows.nil? && affected_rows != expected_affected_rows
102
+ fail ExpectedAffectedRowsError,
103
+ "Expected affected rows, #{expected_affected_rows}, Actual affected rows, #{affected_rows}"
104
+ end
105
+ end
106
+
107
+ def insert(_sql, _params)
108
+ fail 'Firebird uses SEQUENCES, so possibly easier to use 2 executes'
109
+ end
110
+
111
+ # Transaction Semantics
112
+ def begin
113
+ @connection.transaction
114
+ end
115
+
116
+ # Transaction Semantics
117
+ def commit
118
+ @connection.commit
119
+ end
120
+
121
+ # Transaction Semantics
122
+ def rollback
123
+ @connection.rollback
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,103 @@
1
+ require 'FluidDb2'
2
+
3
+ module FluidDb2
4
+ class SqlNotMatchedError < StandardError
5
+ end
6
+
7
+ # A constant way of enabling testing for FluidDb
8
+ class Mock < Base
9
+ attr_reader :hash
10
+
11
+ def initialize(_uri)
12
+ @hash = {}
13
+ @verbose = false
14
+ end
15
+
16
+ def verbose
17
+ @verbose = true
18
+ self
19
+ end
20
+
21
+ def connect
22
+ end
23
+
24
+ def close
25
+ end
26
+
27
+ def get_sql_from_hash(sql)
28
+ fail SqlNotMatchedError, sql unless @hash.key?(sql)
29
+
30
+ @hash[sql]
31
+ end
32
+
33
+ def query_for_array(sql, params = [])
34
+ sql = format_to_sql(sql, params)
35
+ puts "FluidDb::Mock.query_for_array. sql: #{sql}" if @verbose == true
36
+
37
+ results = get_sql_from_hash(sql)
38
+ case results.length
39
+ when 0
40
+ fail FluidDb::NoDataFoundError
41
+ when 1
42
+ return results.first
43
+ else
44
+ fail FluidDb::TooManyRowsError
45
+ end
46
+ end
47
+
48
+ def query_for_value(sql, params = [])
49
+ sql = format_to_sql(sql, params)
50
+ puts "FluidDb::Mock.queryForValue. sql: #{sql}" if @verbose == true
51
+
52
+ results = get_sql_from_hash(sql)
53
+ case results.length
54
+ when 0
55
+ fail FluidDb::NoDataFoundError
56
+ when 1
57
+ return results.first.first[1]
58
+ else
59
+ fail FluidDb::TooManyRowsError
60
+ end
61
+ @hash[sql]
62
+ end
63
+
64
+ def query_for_resultset(sql, params = [])
65
+ sql = format_to_sql(sql, params)
66
+ puts "FluidDb::Mock.queryForResultset. sql: #{sql}" if @verbose == true
67
+ get_sql_from_hash(sql)
68
+ end
69
+
70
+ def execute(sql, params = [], _expected_affected_rows = nil)
71
+ sql = format_to_sql(sql, params)
72
+ puts "FluidDb::Mock.execute. sql: #{sql}" if @verbose == true
73
+ get_sql_from_hash(sql)
74
+ end
75
+
76
+ def insert(_sql, _params)
77
+ fail 'Mock uses SEQUENCES, so possibly easier to use 2 executes'
78
+ end
79
+
80
+ def add_sql(sql, result)
81
+ raise TypeError.new( "Expecting an Array of Hashes, eg [{'field1'=>1, 'field2'=>2}]. Note, the Array may be empty" ) unless result.is_a? Array
82
+
83
+ @hash[sql] = result
84
+ end
85
+
86
+ def add_sql_with_params(sql, params, result)
87
+ sql = format_to_sql(sql, params)
88
+ add_sql(sql, result)
89
+ end
90
+
91
+ # Transaction Semantics
92
+ def begin
93
+ end
94
+
95
+ # Transaction Semantics
96
+ def commit
97
+ end
98
+
99
+ # Transaction Semantics
100
+ def rollback
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,89 @@
1
+ require 'FluidDb2'
2
+ require 'mysql'
3
+
4
+ module FluidDb2
5
+ # Mysql
6
+ class Mysql < Base
7
+ # Connect to Db.
8
+ #
9
+ # @param [String] uri a location for the resource to which we will attach,
10
+ # eg mysql://user:pass@127.0.0.1/foo
11
+ def connect
12
+ uri = @uri
13
+ database = uri.path.sub('/', '')
14
+
15
+ @connection = ::Mysql.new uri.host, uri.user, uri.password, database, nil, nil, ::Mysql::CLIENT_FOUND_ROWS
16
+ end
17
+
18
+ def close
19
+ @connection.close
20
+ end
21
+
22
+ def query_for_array(sql, params = [])
23
+ sql = format_to_sql(sql, params)
24
+ results = @connection.query(sql)
25
+
26
+ case results.num_rows
27
+ when -1
28
+ fail FluidDb::ConnectionError
29
+ when 0
30
+ fail FluidDb::NoDataFoundError
31
+ when 1
32
+ r = results.fetch_hash
33
+ return r
34
+ else
35
+ fail FluidDb::TooManyRowsError
36
+ end
37
+ end
38
+
39
+ def query_for_value(sql, params = [])
40
+ sql = format_to_sql(sql, params)
41
+ results = @connection.query(sql)
42
+
43
+ case results.num_rows
44
+ when -1
45
+ fail FluidDb::ConnectionError
46
+ when 0
47
+ fail FluidDb::NoDataFoundError
48
+ when 1
49
+ r = nil
50
+ results.each do |row|
51
+ r = row
52
+ end
53
+ return r[0]
54
+ else
55
+ fail FluidDb::TooManyRowsError
56
+ end
57
+ end
58
+
59
+ def query_for_resultset(sql, params = [])
60
+ sql = format_to_sql(sql, params)
61
+ results = @connection.query(sql)
62
+
63
+ case results.num_rows
64
+ when -1
65
+ fail FluidDb::ConnectionError
66
+ else
67
+ list = []
68
+ results.each_hash do |row|
69
+ list.push row
70
+ end
71
+ return list
72
+ end
73
+ end
74
+
75
+ def execute(sql, params = [], expected_affected_rows = nil)
76
+ sql = format_to_sql(sql, params)
77
+ @connection.query(sql)
78
+
79
+ if !expected_affected_rows.nil? && @connection.affected_rows != expected_affected_rows
80
+ fail ExpectedAffectedRowsError, "Expected affected rows, #{expected_affected_rows}, Actual affected rows, #{@connection.affected_rows}"
81
+ end
82
+ end
83
+
84
+ def insert(sql, params)
85
+ execute(sql, params)
86
+ @connection.insert_id
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,92 @@
1
+ require 'FluidDb2'
2
+ require 'mysql2'
3
+
4
+ module FluidDb2
5
+ class Mysql2 < Base
6
+ # Connect to Db.
7
+ #
8
+ # @param [String] uri a location for the resource to which we will attach,
9
+ # eg mysql://user:pass@127.0.0.1/foo
10
+ def connect
11
+ uri = @uri
12
+ @connection = ::Mysql2::Client.new(:host => uri.host,
13
+ :database => uri.path.sub('/', ''),
14
+ :username => uri.user,
15
+ :flags => ::Mysql2::Client::FOUND_ROWS)
16
+ end
17
+
18
+ def close
19
+ @connection.close
20
+ end
21
+
22
+ def query_for_array(sql, params = [])
23
+ sql = format_to_sql(sql, params)
24
+ results = @connection.query(sql)
25
+
26
+ case results.count
27
+ when -1
28
+ fail FluidDb::ConnectionError
29
+ when 0
30
+ fail FluidDb::NoDataFoundError
31
+ when 1
32
+ r = nil
33
+ results.each do |row|
34
+ r = row
35
+ end
36
+ return r
37
+ else
38
+ fail FluidDb::TooManyRowsError
39
+ end
40
+ end
41
+
42
+ def query_for_value(sql, params = [])
43
+ sql = format_to_sql(sql, params)
44
+ results = @connection.query(sql, :as => :array)
45
+
46
+ case results.count
47
+ when -1
48
+ fail FluidDb::ConnectionError
49
+ when 0
50
+ fail FluidDb::NoDataFoundError
51
+ when 1
52
+ r = nil
53
+ results.each do |row|
54
+ r = row
55
+ end
56
+ return r[0]
57
+ else
58
+ fail FluidDb::TooManyRowsError
59
+ end
60
+ end
61
+
62
+ def query_for_resultset(sql, params = [])
63
+ sql = format_to_sql(sql, params)
64
+ results = @connection.query(sql)
65
+
66
+ case results.count
67
+ when -1
68
+ fail FluidDb::ConnectionError
69
+ else
70
+ list = []
71
+ results.each do |row|
72
+ list.push row
73
+ end
74
+ return list
75
+ end
76
+ end
77
+
78
+ def execute(sql, params = [], expected_affected_rows = nil)
79
+ sql = format_to_sql(sql, params)
80
+ @connection.query(sql)
81
+
82
+ if !expected_affected_rows.nil? && @connection.affected_rows != expected_affected_rows
83
+ raise ExpectedAffectedRowsError.new( "Expected affected rows, #{expected_affected_rows}, Actual affected rows, #{@connection.affected_rows}")
84
+ end
85
+ end
86
+
87
+ def insert(sql, params)
88
+ execute(sql, params)
89
+ @connection.last_id
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,128 @@
1
+ require 'FluidDb2'
2
+ require 'pg'
3
+
4
+ module FluidDb2
5
+ # Pgsql
6
+ class Pgsql < Base
7
+ # Connect to Db.
8
+ #
9
+ # @param [String] uri a location for the resource to which we will attach,
10
+ # eg mysql://user:pass@127.0.0.1/foo
11
+ def connect
12
+ uri = @uri
13
+ host = uri.host
14
+ dbname = uri.path.sub('/', '')
15
+
16
+ hash = Hash['host', host, 'dbname', dbname]
17
+ hash['port'] = uri.port unless uri.port.nil?
18
+ hash['user'] = uri.user unless uri.user.nil?
19
+ hash['password'] = uri.password unless uri.password.nil?
20
+
21
+ @connection = PG.connect(hash)
22
+ end
23
+
24
+ def close
25
+ @connection.close
26
+ end
27
+
28
+ def query_for_array(sql, params = [])
29
+ sql = FluidDb2.format_to_sql(sql, params)
30
+ results = @connection.exec(sql)
31
+
32
+ case results.num_tuples
33
+ when -1
34
+ fail FluidDb2::ConnectionError
35
+ when 0
36
+ fail FluidDb2::NoDataFoundError
37
+ when 1
38
+ return FluidDb2.convert_tuple_to_hash(results.fields, results, 0)
39
+ else
40
+ fail FluidDb2::TooManyRowsError
41
+ end
42
+ end
43
+
44
+ def query_for_value(sql, params = [])
45
+ sql = FluidDb2.format_to_sql(sql, params)
46
+ results = @connection.exec(sql)
47
+
48
+ case results.num_tuples
49
+ when -1
50
+ fail FluidDb2::ConnectionError
51
+ when 0
52
+ fail FluidDb2::NoDataFoundError
53
+ when 1
54
+ return results.getvalue(0, 0)
55
+ else
56
+ fail FluidDb2::TooManyRowsError
57
+ end
58
+ end
59
+
60
+ def query_for_resultset(sql, params = [])
61
+ sql = FluidDb2.format_to_sql(sql, params)
62
+ results = @connection.exec(sql)
63
+
64
+ case results.num_tuples
65
+ when -1
66
+ fail FluidDb2::ConnectionError
67
+ else
68
+ list = []
69
+ fields = results.fields
70
+ 0.upto(results.ntuples - 1) do |nbr|
71
+ list.push FluidDb2.convert_tuple_to_hash(fields, results, nbr)
72
+ end
73
+
74
+ return list
75
+ end
76
+ end
77
+
78
+ def execute(sql, params = [], expected_affected_rows = nil)
79
+ sql = FluidDb2.format_to_sql(sql, params)
80
+
81
+ verbose_log "#{self.class.name}.execute. #{sql}"
82
+ r = @connection.exec(sql)
83
+
84
+ if !expected_affected_rows.nil? && r.cmd_tuples != expected_affected_rows
85
+ fail ExpectedAffectedRowsError, "Expected affected rows, #{expected_affected_rows}, Actual affected rows, #{r.cmd_tuples}"
86
+ end
87
+ rescue PG::Error => e
88
+ raise DuplicateKeyError(e.message) unless e.message.index('duplicate key value violates unique constraint').nil?
89
+ raise e
90
+ end
91
+
92
+ def exec_params(sql, params = [], expected_affected_rows = nil)
93
+ parts = sql.split('?')
94
+ sql = ''
95
+ parts.each_with_index do |p, idx|
96
+ sql += p
97
+ sql += "$#{idx + 1}" if idx < parts.length - 1
98
+ end
99
+ r = @connection.exec_params(sql, params)
100
+
101
+ if !expected_affected_rows.nil? and r.cmd_tuples != expected_affected_rows
102
+ fail ExpectedAffectedRowsError, "Expected affected rows, #{expected_affected_rows}, Actual affected rows, #{r.cmd_tuples}"
103
+ end
104
+ rescue PG::Error => e
105
+ raise DuplicateKeyError(e.message) unless e.message.index("duplicate key value violates unique constraint").nil?
106
+ raise e
107
+ end
108
+
109
+ # Transaction Semantics
110
+ def begin
111
+ @connection.exec('BEGIN', [])
112
+ end
113
+
114
+ # Transaction Semantics
115
+ def commit
116
+ @connection.exec('COMMIT', [])
117
+ end
118
+
119
+ # Transaction Semantics
120
+ def rollback
121
+ @connection.exec('ROLLBACK', [])
122
+ end
123
+
124
+ def insert(_sql, _params)
125
+ fail 'Pgsql uses SEQUENCES, so possibly easier to use 2 executes'
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,111 @@
1
+ require 'FluidDb2'
2
+ require 'sqlite3'
3
+
4
+ module FluidDb2
5
+ # SQLite
6
+ class SQLite < Base
7
+ # Connect to Db.
8
+ #
9
+ # @param [String] uri a location for the resource to which we will attach,
10
+ # eg mysql://user:pass@127.0.0.1/foo
11
+ def connect
12
+ uri = @uri
13
+ @connection = SQLite3::Database.new uri.path
14
+ end
15
+
16
+ def close
17
+ @connection.close
18
+ end
19
+
20
+ def query_for_array(sql, params = [])
21
+ sql = format_to_sql(sql, params)
22
+ @connection.results_as_hash = true
23
+ results = @connection.execute(sql)
24
+
25
+ case results.length
26
+ when -1
27
+ fail FluidDb::ConnectionError
28
+ when 0
29
+ fail FluidDb::NoDataFoundError
30
+ when 1
31
+ return results[0]
32
+ else
33
+ fail FluidDb::TooManyRowsError
34
+ end
35
+ end
36
+
37
+ def query_for_value(sql, params = [])
38
+ sql = format_to_sql(sql, params)
39
+ @connection.results_as_hash = false
40
+ results = @connection.execute(sql)
41
+
42
+ case results.length
43
+ when -1
44
+ fail FluidDb::ConnectionError
45
+ when 0
46
+ fail FluidDb::NoDataFoundError
47
+ when 1
48
+ return results[0][0]
49
+ else
50
+ fail FluidDb::TooManyRowsError
51
+ end
52
+ end
53
+
54
+ def query_for_resultset(sql, params = [])
55
+ sql = format_to_sql(sql, params)
56
+ @connection.results_as_hash = true
57
+ results = @connection.execute(sql)
58
+
59
+ case results.length
60
+ when -1
61
+ fail FluidDb::ConnectionError
62
+ else
63
+ return results
64
+ end
65
+ end
66
+
67
+ def execute(sql, params = [], expected_affected_rows = nil)
68
+ sql = format_to_sql(sql, params)
69
+
70
+ verbose_log "#{self.class.name}.execute. #{sql}"
71
+ r = @connection.execute(sql)
72
+
73
+ if !expected_affected_rows.nil? && r.changes != expected_affected_rows
74
+ fail ExpectedAffectedRowsError, "Expected affected rows, #{expected_affected_rows}, Actual affected rows, #{r.cmd_tuples}"
75
+ end
76
+ end
77
+
78
+ def exec_params(sql, params = [], expected_affected_rows = nil)
79
+ parts = sql.split('?')
80
+ sql = ''
81
+ parts.each_with_index do |p, idx|
82
+ sql += p
83
+ sql += "$#{idx + 1}" if idx < parts.length - 1
84
+ end
85
+ r = @connection.exec_params(sql, params)
86
+
87
+ if !expected_affected_rows.nil? && r.changes != expected_affected_rows
88
+ fail ExpectedAffectedRowsError, "Expected affected rows, #{expected_affected_rows}, Actual affected rows, #{r.cmd_tuples}"
89
+ end
90
+ end
91
+
92
+ # Transaction Semantics
93
+ def begin
94
+ @connection.transaction
95
+ end
96
+
97
+ # Transaction Semantics
98
+ def commit
99
+ @connection.commit
100
+ end
101
+
102
+ # Transaction Semantics
103
+ def rollback
104
+ @connection.rollback
105
+ end
106
+
107
+ def insert(_sql, _params)
108
+ fail 'SQLite uses SEQUENCES, so possibly easier to use 2 executes'
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,139 @@
1
+ require 'FluidDb2'
2
+ require 'tiny_tds'
3
+ require 'cgi'
4
+
5
+ module FluidDb2
6
+ class TinyTds < Base
7
+
8
+ # Connect to Db.
9
+ #
10
+ # @param [String] uri a location for the resource to which we will attach,
11
+ # eg tinytds://<user>:<pass>@<dataserver>/<database>
12
+ def connect
13
+ uri = @uri
14
+
15
+ dataserver = uri.host
16
+ database = uri.path.sub('/', '')
17
+ username = URI.unescape(uri.user)
18
+ password = uri.password
19
+
20
+ if dataserver == '' || database == ''
21
+ fail '*** You need to specify both a dataserver and a database for ' \
22
+ 'the tinytds driver. Expected format: ' \
23
+ 'tinytds://<user>:<pass>@<dataserver>/<database>\n' \
24
+ '*** The specified dataserver should have an entry in ' \
25
+ '/etc/freetds/freetds.conf'
26
+ end
27
+
28
+ if username == '' || password == ''
29
+ puts '*** Warning - you will normally need to specify both a username ' \
30
+ 'and password for the tinytds driver to work correctly.'
31
+ end
32
+
33
+ hash = Hash[:username, username,
34
+ :password, password,
35
+ :database, database,
36
+ :dataserver, dataserver]
37
+ fail_over_data_server = nil
38
+ unless uri.query.nil?
39
+ cgi = CGI.parse(uri.query)
40
+ hash[:timeout] = cgi['timeout'][0].to_i if cgi.key?('timeout')
41
+ if cgi.key?('host')
42
+ hash.delete(:dataserver)
43
+ hash[:host] = dataserver
44
+ end
45
+
46
+ fail_over_data_server =
47
+ cgi['failoverdataserver'][0] if cgi.key?('failoverdataserver')
48
+ end
49
+
50
+ begin
51
+ @connection = ::TinyTds::Client.new(hash)
52
+ rescue ::TinyTds::Error => e
53
+ # Message for an incorrect password,
54
+ # Login failed. The login is from an untrusted domain and cannot be used
55
+ # with Windows authentication.
56
+ # Message for unavailable db
57
+ # Cannot open user default database. Login failed.
58
+ if e.message == 'Cannot open user default database. Login failed.' &&
59
+ !fail_over_data_server.nil?
60
+ hash[:dataserver] = fail_over_data_server
61
+ @connection = ::TinyTds::Client.new(hash)
62
+ else
63
+ raise e
64
+ end
65
+ end
66
+
67
+ fail 'Unable to connect to the database' unless @connection.active?
68
+ end
69
+
70
+ def close
71
+ @connection.close
72
+ end
73
+
74
+ def escape_string(input)
75
+ @connection.escape(input)
76
+ end
77
+
78
+ def query_for_array(sql, params = [])
79
+ sql = format_to_sql(sql, params)
80
+ results = @connection.execute(sql)
81
+
82
+ count = 0
83
+ tuple = ''
84
+ results.each do |row|
85
+ count += 1
86
+ fail FluidDb2::TooManyRowsError if count > 1
87
+ tuple = row
88
+ end
89
+ fail FluidDb2::NoDataFoundError if count == 0
90
+ tuple
91
+ end
92
+
93
+ def query_for_value(sql, params = [])
94
+ sql = format_to_sql(sql, params)
95
+ results = @connection.execute(sql)
96
+
97
+ count = 0
98
+ value = ''
99
+ results.each do |row|
100
+ count += 1
101
+ fail FluidDb2::TooManyRowsError if count > 1
102
+ value = row[results.fields[0]]
103
+ end
104
+
105
+ fail FluidDb2::NoDataFoundError if count == 0
106
+
107
+ value
108
+ end
109
+
110
+ def query_for_resultset(sql, params = [])
111
+ sql = format_to_sql(sql, params)
112
+ results = @connection.execute(sql)
113
+
114
+ list = []
115
+ results.each do |row|
116
+ list << row
117
+ end
118
+
119
+ list
120
+ end
121
+
122
+ def execute(sql, params = [], expected_affected_rows = nil)
123
+ sql = format_to_sql(sql, params)
124
+ r = @connection.execute(sql)
125
+ r.each
126
+
127
+ if !expected_affected_rows.nil? &&
128
+ r.affected_rows != expected_affected_rows
129
+ msg = "Expected affected rows, #{expected_affected_rows}, " \
130
+ "Actual affected rows, #{r.affected_rows}"
131
+ fail ExpectedAffectedRowsError, msg
132
+ end
133
+ end
134
+
135
+ def insert(_sql, _params)
136
+ fail 'Not implemented'
137
+ end
138
+ end
139
+ end
data/lib/fluiddb2.rb ADDED
@@ -0,0 +1,210 @@
1
+ require 'date'
2
+ require 'time'
3
+ require 'uri'
4
+
5
+ require 'fluiddb/db'
6
+
7
+ # FluidDb2
8
+ module FluidDb2
9
+ class ConnectionError < StandardError
10
+ end
11
+ class NoDataFoundError < StandardError
12
+ end
13
+ class TooManyRowsError < StandardError
14
+ end
15
+ class ParamTypeNotSupportedError < StandardError
16
+ end
17
+ class ExpectedAffectedRowsError < StandardError
18
+ end
19
+ class IncorrectNumberOfParametersError < StandardError
20
+ end
21
+ class DuplicateKeyError < StandardError
22
+ end
23
+
24
+ def self.db(uri)
25
+ uri = URI.parse(uri) if uri.is_a? String
26
+
27
+ case uri.scheme
28
+ when 'mysql'
29
+ require 'fluiddb2/mysql'
30
+ return FluidDb2::Mysql.new(uri)
31
+ when 'mysql2'
32
+ require 'fluiddb2/mysql2'
33
+ return FluidDb2::Mysql2.new(uri)
34
+ when 'pgsql'
35
+ require 'fluiddb2/pgsql'
36
+ return FluidDb2::Pgsql.new(uri)
37
+ when 'fb'
38
+ require 'fluiddb2/firebird'
39
+ return FluidDb2::Firebird.new(uri)
40
+ when 'mock'
41
+ require 'fluiddb2/Mock'
42
+ return FluidDb2::Mock.new(uri)
43
+ when 'tinytds'
44
+ require 'fluiddb2/tiny_tds'
45
+ return FluidDb2::TinyTds.new(uri)
46
+ when 'sqlite'
47
+ require 'fluiddb2/sqlite'
48
+ return FluidDb2::SQLite.new(uri)
49
+ else
50
+ abort("Scheme, #{uri.scheme}, not recognised when configuring creating " \
51
+ 'db connection')
52
+ end
53
+ end
54
+
55
+ def self.splice_sql(sql, params)
56
+ fail IncorrectNumberOfParametersError if params.length != sql.count('?')
57
+
58
+ sql_out = ''
59
+ sql.split('?').each_with_index do |s, idx|
60
+ sql_out += s
61
+ sql_out += params[idx] unless params[idx].nil?
62
+ end
63
+
64
+ sql_out
65
+ end
66
+
67
+ def self.escape_string(input)
68
+ input.split("'").join("''")
69
+ end
70
+
71
+ def self.format_to_sql(sql, params = nil)
72
+ return sql if params.nil? || params.count == 0
73
+
74
+ params.each_with_index do |v, idx|
75
+ if v.is_a? String
76
+ v = "'#{escape_string(v)}'"
77
+ elsif v.is_a? DateTime
78
+ v = "'" + v.strftime('%Y-%m-%d %H:%M:%S.%6N %z') + "'"
79
+ elsif v.is_a? Time
80
+ v = "'" + v.strftime('%Y-%m-%d %H:%M:%S.%6N %z') + "'"
81
+ elsif v.is_a? Date
82
+ v = "'#{v}'"
83
+ elsif v.is_a? Numeric
84
+ v = v.to_s
85
+ elsif v.is_a? TrueClass
86
+ v = 'true'
87
+ elsif v.is_a? FalseClass
88
+ v = 'false'
89
+ elsif v.nil?
90
+ v = 'NULL'
91
+ else
92
+ fail ParamTypeNotSupportedError,
93
+ "Name of unknown param type, #{v.class.name}, for sql, #{sql}"
94
+ end
95
+ params[idx] = v
96
+ end
97
+
98
+ sql_out = splice_sql(sql, params)
99
+ if @verbose == true
100
+ puts self.class.name
101
+ puts sql
102
+ puts params.join(',')
103
+ puts sql_out
104
+ end
105
+
106
+ sql_out
107
+ end
108
+
109
+ def self.convert_tuple_to_hash(fields, tuple, j)
110
+ hash = {}
111
+ 0.upto(fields.length - 1).each do |i|
112
+ hash[fields[i].to_s] = tuple.getvalue(j, i)
113
+ end
114
+
115
+ hash
116
+ end
117
+
118
+ # Base
119
+ class Base
120
+ attr_writer :verbose
121
+ attr_reader :connection
122
+
123
+ # Constructor.
124
+ #
125
+ # @param [String] uri a location for the resource to which we will attach,
126
+ # eg mysql://user:pass@127.0.0.1/foo
127
+ def initialize(uri)
128
+ if uri.is_a? String
129
+ @uri = URI.parse(uri)
130
+ else
131
+ @uri = uri
132
+ end
133
+
134
+ connect
135
+
136
+ @verbose = !ENV['VERBOSE'].nil?
137
+ end
138
+
139
+ def verbose_log(string)
140
+ puts string if @verbose == true
141
+ end
142
+
143
+ def connect
144
+ fail NotImplementedError, "You must implement 'connect'."
145
+ end
146
+
147
+ def close
148
+ fail NotImplementedError, "You must implement 'close'."
149
+ end
150
+
151
+ def reconnect
152
+ close
153
+ connect
154
+ end
155
+
156
+ # Return a single row from the database, given the sql parameter.
157
+ # Throws an error for no data.
158
+ # Throws an error for more than 1 row
159
+ #
160
+ # @param [String] sql The SELECT statement to run
161
+ # @param [Array] parama The parameters to be added to the sql query. Ruby
162
+ # types are used to determine formatting and escaping.
163
+ def query_for_array(_sql, _params)
164
+ fail NotImplementedError, "You must implement 'queryForArray'."
165
+ end
166
+
167
+ # Return a single value is returned from a single row from the database,
168
+ # given the sql parameter.
169
+ # Throws an error for no data.
170
+ # Throws an error for more than 1 row
171
+ #
172
+ # @param [String] sql The SELECT statement to run
173
+ # @param [Array] parama The parameters to be added to the sql query. Ruby
174
+ # types are used to determine formatting and escaping.
175
+ def query_for_value(_sql, _params)
176
+ fail NotImplementedError, "You must implement 'queryForValue'."
177
+ end
178
+
179
+ def query_for_resultset(_sql, _params)
180
+ fail NotImplementedError, "You must implement 'queryForResultset'."
181
+ end
182
+
183
+ # Execute an insert, update or delete, then check the impact that statement
184
+ # has on the data.
185
+ #
186
+ # @param [String] sql The SELECT statement to run
187
+ # @param [Array] parama The parameters to be added to the sql query. Ruby
188
+ # types are used to determine formatting and escaping.
189
+ # @param [String] expected_affected_rows The number of rows that should
190
+ # have been updated.
191
+ def execute(_sql, _params, _expected_affected_rows)
192
+ fail NotImplementedError, "You must implement 'execute'."
193
+ end
194
+
195
+ # Transaction Semantics
196
+ def begin
197
+ @connection.execute('BEGIN', [])
198
+ end
199
+
200
+ # Transaction Semantics
201
+ def commit
202
+ @connection.execute('COMMIT', [])
203
+ end
204
+
205
+ # Transaction Semantics
206
+ def rollback
207
+ @connection.execute('ROLLBACK', [])
208
+ end
209
+ end
210
+ end
metadata ADDED
@@ -0,0 +1,52 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fluiddb2
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Guy Irvine
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-07-30 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: A semantic layer for db interaction
14
+ email: guy@guyirvine.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - lib/fluiddb2/firebird.rb
20
+ - lib/fluiddb2/mock.rb
21
+ - lib/fluiddb2/mysql.rb
22
+ - lib/fluiddb2/mysql2.rb
23
+ - lib/fluiddb2/pgsql.rb
24
+ - lib/fluiddb2/sqlite.rb
25
+ - lib/fluiddb2/tiny_tds.rb
26
+ - lib/fluiddb2.rb
27
+ - LICENSE
28
+ - README.md
29
+ homepage: http://rubygems.org/gems/fluiddb2
30
+ licenses: []
31
+ metadata: {}
32
+ post_install_message:
33
+ rdoc_options: []
34
+ require_paths:
35
+ - lib
36
+ required_ruby_version: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ required_rubygems_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ requirements: []
47
+ rubyforge_project:
48
+ rubygems_version: 2.0.14.1
49
+ signing_key:
50
+ specification_version: 4
51
+ summary: FluidDB
52
+ test_files: []