purview 1.0.0.alpha
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.
- checksums.yaml +7 -0
- data/.gitignore +33 -0
- data/.travis.yml +18 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +143 -0
- data/Rakefile +11 -0
- data/TODO +81 -0
- data/lib/purview/columns/base.rb +65 -0
- data/lib/purview/columns/boolean.rb +11 -0
- data/lib/purview/columns/created_timestamp.rb +11 -0
- data/lib/purview/columns/date.rb +11 -0
- data/lib/purview/columns/float.rb +11 -0
- data/lib/purview/columns/id.rb +11 -0
- data/lib/purview/columns/integer.rb +11 -0
- data/lib/purview/columns/money.rb +11 -0
- data/lib/purview/columns/string.rb +11 -0
- data/lib/purview/columns/text.rb +11 -0
- data/lib/purview/columns/time.rb +11 -0
- data/lib/purview/columns/timestamp.rb +11 -0
- data/lib/purview/columns/updated_timestamp.rb +11 -0
- data/lib/purview/columns/uuid.rb +11 -0
- data/lib/purview/columns.rb +14 -0
- data/lib/purview/connections/base.rb +55 -0
- data/lib/purview/connections/mysql.rb +39 -0
- data/lib/purview/connections/postgresql.rb +27 -0
- data/lib/purview/connections.rb +3 -0
- data/lib/purview/databases/base.rb +559 -0
- data/lib/purview/databases/mysql.rb +207 -0
- data/lib/purview/databases/postgresql.rb +210 -0
- data/lib/purview/databases.rb +3 -0
- data/lib/purview/exceptions/base.rb +5 -0
- data/lib/purview/exceptions/could_not_acquire_lock.rb +9 -0
- data/lib/purview/exceptions/lock_already_released.rb +9 -0
- data/lib/purview/exceptions/no_table.rb +9 -0
- data/lib/purview/exceptions/no_window.rb +9 -0
- data/lib/purview/exceptions/rows_outside_window.rb +18 -0
- data/lib/purview/exceptions/table.rb +13 -0
- data/lib/purview/exceptions.rb +7 -0
- data/lib/purview/loaders/base.rb +154 -0
- data/lib/purview/loaders/mysql.rb +81 -0
- data/lib/purview/loaders/postgresql.rb +81 -0
- data/lib/purview/loaders.rb +3 -0
- data/lib/purview/loggers/base.rb +99 -0
- data/lib/purview/loggers/console.rb +11 -0
- data/lib/purview/loggers.rb +2 -0
- data/lib/purview/mixins/helpers.rb +21 -0
- data/lib/purview/mixins/logger.rb +21 -0
- data/lib/purview/mixins.rb +2 -0
- data/lib/purview/parsers/base.rb +39 -0
- data/lib/purview/parsers/csv.rb +49 -0
- data/lib/purview/parsers/tsv.rb +11 -0
- data/lib/purview/parsers.rb +3 -0
- data/lib/purview/pullers/base.rb +19 -0
- data/lib/purview/pullers/uri.rb +66 -0
- data/lib/purview/pullers.rb +2 -0
- data/lib/purview/refinements/object.rb +5 -0
- data/lib/purview/refinements/time.rb +5 -0
- data/lib/purview/refinements.rb +2 -0
- data/lib/purview/structs/base.rb +10 -0
- data/lib/purview/structs/result.rb +7 -0
- data/lib/purview/structs/window.rb +7 -0
- data/lib/purview/structs.rb +3 -0
- data/lib/purview/tables/base.rb +140 -0
- data/lib/purview/tables/raw.rb +13 -0
- data/lib/purview/tables.rb +2 -0
- data/lib/purview/types/base.rb +9 -0
- data/lib/purview/types/boolean.rb +9 -0
- data/lib/purview/types/date.rb +9 -0
- data/lib/purview/types/float.rb +9 -0
- data/lib/purview/types/integer.rb +9 -0
- data/lib/purview/types/money.rb +9 -0
- data/lib/purview/types/string.rb +9 -0
- data/lib/purview/types/text.rb +9 -0
- data/lib/purview/types/time.rb +9 -0
- data/lib/purview/types/timestamp.rb +9 -0
- data/lib/purview/types/uuid.rb +9 -0
- data/lib/purview/types.rb +11 -0
- data/lib/purview/version.rb +3 -0
- data/lib/purview.rb +27 -0
- data/purview.gemspec +29 -0
- data/spec/spec_helper.rb +5 -0
- metadata +210 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 4753e561eece41d8838e916c7914ae3b3d155b3b
|
4
|
+
data.tar.gz: 8430637788951088580baa1f533540968974f28c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 742e3ab26e898924c9054b461d41c700144859910b96ccfd2df6ac6e9d51bb2945a61f73282445f7c239c8adfeb4cc53bdf518aad195b91cc931cdeca24639bb
|
7
|
+
data.tar.gz: 43d8b1a2b5f6cf401a33e254a8b63169b1764fe3a88b83391a91b6068203b0bd02df339746982580864aba2a3c3f9ae0ba079fd938f31fe687d37e38b7aff987
|
data/.gitignore
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
/.config
|
4
|
+
/InstalledFiles
|
5
|
+
/coverage/
|
6
|
+
/pkg/
|
7
|
+
/script/
|
8
|
+
/spec/reports/
|
9
|
+
/test/tmp/
|
10
|
+
/test/version_tmp/
|
11
|
+
/tmp/
|
12
|
+
tags
|
13
|
+
|
14
|
+
## Specific to RubyMotion:
|
15
|
+
.dat*
|
16
|
+
.repl_history
|
17
|
+
build/
|
18
|
+
|
19
|
+
## Documentation cache and generated files:
|
20
|
+
/.yardoc/
|
21
|
+
/_yardoc/
|
22
|
+
/doc/
|
23
|
+
/rdoc/
|
24
|
+
|
25
|
+
## Environment normalisation:
|
26
|
+
/.bundle/
|
27
|
+
/lib/bundler/man/
|
28
|
+
|
29
|
+
## Ruby artifacts:
|
30
|
+
.ruby-gemset
|
31
|
+
.ruby-version
|
32
|
+
.rvmrc
|
33
|
+
Gemfile.lock
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 Jonathan W. Zaleski
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
# Purview
|
2
|
+
|
3
|
+
[](http://travis-ci.org/jzaleski/purview)
|
4
|
+
[](https://gemnasium.com/jzaleski/purview)
|
5
|
+
|
6
|
+
A framework designed to simplify data warehousing
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
Add this line to your application's Gemfile:
|
11
|
+
|
12
|
+
gem 'purview'
|
13
|
+
|
14
|
+
And then execute:
|
15
|
+
|
16
|
+
$ bundle
|
17
|
+
|
18
|
+
Or install it yourself as:
|
19
|
+
|
20
|
+
$ gem install purview
|
21
|
+
|
22
|
+
## Usage
|
23
|
+
|
24
|
+
Load the `MySQL` client (for `PostgreSQL` simply change 'mysql2' to 'pg')
|
25
|
+
```ruby
|
26
|
+
require 'mysql2'
|
27
|
+
```
|
28
|
+
|
29
|
+
Set the database-name (this can be anything, but it must exist)
|
30
|
+
```ruby
|
31
|
+
database_name = :data_warehouse
|
32
|
+
```
|
33
|
+
|
34
|
+
Combine all the configuration options and instantiate the `Database` (for
|
35
|
+
`PostgreSQL` simply change `MySQL` to `PostgreSQL`)
|
36
|
+
```ruby
|
37
|
+
database_opts = {}
|
38
|
+
|
39
|
+
database = Purview::Databases::MySQL.new(
|
40
|
+
database_name,
|
41
|
+
database_opts
|
42
|
+
)
|
43
|
+
```
|
44
|
+
|
45
|
+
Set the table-name (this can be anything, but it must exist)
|
46
|
+
```ruby
|
47
|
+
table_name = :users
|
48
|
+
```
|
49
|
+
|
50
|
+
Define the `Column(s)` (available column-types: `Boolean`, `CreatedTimestamp`,
|
51
|
+
`Date`, `Float`, `Id`, `Integer`, `Money`, `String`, `Text`, `Time`, `Timestamp`,
|
52
|
+
`UpdatedTimestamp` & `UUID` -- the `Id`, `CreatedTimestamp` & `UpdatedTimestamp`
|
53
|
+
columns are required for all tables)
|
54
|
+
```ruby
|
55
|
+
columns = [
|
56
|
+
Purview::Columns::Id.new(:id),
|
57
|
+
Purview::Columns::String.new(:name, :nullable => false),
|
58
|
+
Purview::Columns::String.new(:email, :nullable => false, :limit => 100),
|
59
|
+
Purview::Columns::CreatedTimestamp.new(:created_at),
|
60
|
+
Purview::Columns::UpdatedTimestamp.new(:updated_at),
|
61
|
+
]
|
62
|
+
```
|
63
|
+
|
64
|
+
Configure the `Puller` (available puller-types: `URI`)
|
65
|
+
```ruby
|
66
|
+
puller_opts = {
|
67
|
+
:type => Purview::Pullers::URI,
|
68
|
+
:uri => 'http://feed.test.com/users',
|
69
|
+
}
|
70
|
+
```
|
71
|
+
|
72
|
+
Configure the `Parser` (available parser-types: `CSV` & `TSV`)
|
73
|
+
```ruby
|
74
|
+
parser_opts = {
|
75
|
+
:type => Purview::Parsers::TSV,
|
76
|
+
}
|
77
|
+
```
|
78
|
+
|
79
|
+
Configure the `Loader` (for `PostgreSQL` simply change `MySQL` to `PostgreSQL`)
|
80
|
+
```ruby
|
81
|
+
loader_opts = {
|
82
|
+
:type => Purview::Loaders::MySQL,
|
83
|
+
}
|
84
|
+
```
|
85
|
+
|
86
|
+
Configure the `starting_timestamp` (this is the min-date to pull and can vary
|
87
|
+
between `Table(s)`)
|
88
|
+
```ruby
|
89
|
+
starting_timestamp = Time.parse('2012-01-01 00:00:00Z')
|
90
|
+
```
|
91
|
+
|
92
|
+
Combine all the configuration options and instantiate the `Table`
|
93
|
+
```ruby
|
94
|
+
table_opts = {
|
95
|
+
:columns => columns,
|
96
|
+
:database => database,
|
97
|
+
:loader => loader_opts,
|
98
|
+
:parser => parser_opts,
|
99
|
+
:puller => puller_opts,
|
100
|
+
:starting_timestamp => starting_timestamp,
|
101
|
+
}
|
102
|
+
|
103
|
+
table = Purview::Tables::Raw.new(
|
104
|
+
table_name,
|
105
|
+
table_opts
|
106
|
+
)
|
107
|
+
```
|
108
|
+
|
109
|
+
Add the `Table` to the `Database` (schema). In order for [the] `Table` to be
|
110
|
+
`sync[ed]` it *must* be added to [the] `Database`
|
111
|
+
```ruby
|
112
|
+
database.add_table(table)
|
113
|
+
```
|
114
|
+
|
115
|
+
Create the `Table`. Recommended for testing purposes *only*. For production
|
116
|
+
environments you will likely want an external process to manage the schema (for
|
117
|
+
`PostgreSQL` simply change `Mysql2::Error` to `PG::DuplicateTable`)
|
118
|
+
```ruby
|
119
|
+
begin
|
120
|
+
database.create_table(
|
121
|
+
database.connect,
|
122
|
+
table
|
123
|
+
)
|
124
|
+
rescue Mysql2::Error; end
|
125
|
+
```
|
126
|
+
|
127
|
+
Sync the `Database`. This process will select a [candidate] `Table`, pull data
|
128
|
+
from its [remote-]source and reconcile the new data against the main-table (e.g.
|
129
|
+
perform `INSERTs`, `UPDATEs` and `DELETEs`). When multiple `Table(s)` are
|
130
|
+
configured the least recently pulled and available (`enabled` and not `locked`)
|
131
|
+
`Table` will be selected (you will likely want to configure some process to load
|
132
|
+
the schema run the `sync` at regularly scheduled intervals)
|
133
|
+
```ruby
|
134
|
+
database.sync
|
135
|
+
```
|
136
|
+
|
137
|
+
## Contributing
|
138
|
+
|
139
|
+
1. Fork it ( http://github.com/jzaleski/purview/fork )
|
140
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
141
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
142
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
143
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/TODO
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
... CLOSED TASKS ....
|
2
|
+
|
3
|
+
* Handle blank values a bit more intelligently (all blanks -> nil)
|
4
|
+
* Add support for INSERTs, UPDATEs and DELETEs (of table-data)
|
5
|
+
* Deal w/ NULL values
|
6
|
+
* Deal w/ BOOL values
|
7
|
+
* Build out MVP for PostgreSQL loader
|
8
|
+
* Create temporary table from table-metadata and load all of the parsed rows
|
9
|
+
into it (build create, update and delete logic atop the temporary table)
|
10
|
+
* Map [object-]types to database types (in the loader?)
|
11
|
+
* Add windowing to pulls (simulate w/ 2 different data-files if necessary)
|
12
|
+
* Store max-timestamp pulled (NULL or previous window + window size)
|
13
|
+
* Add configuration to `Table` for window-size (in the future this can scale
|
14
|
+
dynamically)
|
15
|
+
* Include [window] timestamps in query-string for pull[er]
|
16
|
+
* Figure out an intelligent way to solve the chicken <-> egg problem for
|
17
|
+
`table_metadata`
|
18
|
+
* Fail fast if there are no candidate tables (`next_table`)
|
19
|
+
* Ensure that the puller does not try to go into the future (`next_window`)
|
20
|
+
* Add in a primitive logging facility (can be temporary)
|
21
|
+
* Enhance `next_table_sql`
|
22
|
+
* Lock tables (feeds) during pull, parse and load
|
23
|
+
* Verify table-locking mechanism behaves as expected
|
24
|
+
* Ability to drop a table
|
25
|
+
* Reorder args: "table, table_name" to: "table_name, table"
|
26
|
+
* Reorder args: "table, index_name, *columns" to "index_name, table, *columns"
|
27
|
+
* Ability to create an index
|
28
|
+
* Ability to drop an index
|
29
|
+
* Indices on `created_at` and `updated_at` columns
|
30
|
+
* Gracefully handle table {,un}locking in case of error
|
31
|
+
* Add debugging code
|
32
|
+
* Raise no-window & no-table exceptions (making them catchable upstream)
|
33
|
+
* Minimize race-condition around table-locking (use where on UPDATE, raise if
|
34
|
+
zero rows are updated)
|
35
|
+
* Log number of delete, inserts and updates in `loader`
|
36
|
+
* `load_temporary_table` should bomb if there is data outside the window
|
37
|
+
* Delete from outside window where id in temporary table
|
38
|
+
* Rename `data` to `rows` in `execute`
|
39
|
+
* Rename `Postgres` to `PostgreSQL`
|
40
|
+
* Finish PostgreSQL support (requires `pg` gem)
|
41
|
+
* Finish MySQL support (requires `mysql2` gem)
|
42
|
+
* Usage/configuration examples in README.md
|
43
|
+
|
44
|
+
... ONGOING TASKS ...
|
45
|
+
|
46
|
+
* Add tests
|
47
|
+
* Add comments where appropriate
|
48
|
+
* DRY/Refactor
|
49
|
+
|
50
|
+
... OPEN TASKS ....
|
51
|
+
|
52
|
+
* Create table class to encapulate `table_metadata` logic
|
53
|
+
* Build out change-log tables
|
54
|
+
* Build out canonical, fact and aggregate tables (and related transforms)
|
55
|
+
* Configurable re-pull window (do this automatically once up to current?)
|
56
|
+
* Add schema management capabilities (detect schema-deltas and suggestion
|
57
|
+
modifications)
|
58
|
+
|
59
|
+
... DEFERRED TASKS ...
|
60
|
+
|
61
|
+
* Fix JRuby bundle/build
|
62
|
+
|
63
|
+
... CLOSED QUESTIONS ...
|
64
|
+
|
65
|
+
* Where is the best place to create the connection?
|
66
|
+
* Immediately? Then it's open during the pull, etc.
|
67
|
+
* Lazily? Makes for connection logic all over the place
|
68
|
+
* Perhaps it can be centralized entirely into the `Database` class?
|
69
|
+
* Consider using ISO-8601 timestamps in query-string (not sure on this one, it
|
70
|
+
is slightly more fragile because of URL-encoding, etc.)
|
71
|
+
|
72
|
+
... OPEN QUESTIONS ...
|
73
|
+
|
74
|
+
* Parallel pulls from the same "source"? Can this be done in one request?
|
75
|
+
* Rename columns during pull/parse `source_name` & `target_name` perhaps? Maybe
|
76
|
+
create a new type of column?
|
77
|
+
|
78
|
+
... INTEGRATION CONSIDERATIONS ...
|
79
|
+
|
80
|
+
* Deployment
|
81
|
+
* Scheduling (Daemon? CRON?)
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Purview
|
2
|
+
module Columns
|
3
|
+
class Base
|
4
|
+
attr_reader :name
|
5
|
+
|
6
|
+
def initialize(name, opts={})
|
7
|
+
@name = name.to_sym
|
8
|
+
@opts = default_opts.merge(opts)
|
9
|
+
end
|
10
|
+
|
11
|
+
def default
|
12
|
+
opts[:default]
|
13
|
+
end
|
14
|
+
|
15
|
+
def default?
|
16
|
+
!!default
|
17
|
+
end
|
18
|
+
|
19
|
+
def limit
|
20
|
+
opts[:limit]
|
21
|
+
end
|
22
|
+
|
23
|
+
def limit?
|
24
|
+
!!limit
|
25
|
+
end
|
26
|
+
|
27
|
+
def nullable
|
28
|
+
coalesce(opts[:nullable], true)
|
29
|
+
end
|
30
|
+
|
31
|
+
def nullable?
|
32
|
+
!!nullable
|
33
|
+
end
|
34
|
+
|
35
|
+
def parse(value)
|
36
|
+
blank = blank?(value)
|
37
|
+
return nil if blank && nullable?
|
38
|
+
raise %{Unexpected blank value for column: "#{name}"} if blank
|
39
|
+
type.parse(value)
|
40
|
+
end
|
41
|
+
|
42
|
+
def primary_key
|
43
|
+
opts[:primary_key]
|
44
|
+
end
|
45
|
+
|
46
|
+
def primary_key?
|
47
|
+
!!primary_key
|
48
|
+
end
|
49
|
+
|
50
|
+
def type
|
51
|
+
coalesce(opts[:type], Purview::Types::String)
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
include Purview::Mixins::Helpers
|
57
|
+
|
58
|
+
attr_reader :opts
|
59
|
+
|
60
|
+
def default_opts
|
61
|
+
{}
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'purview/columns/base'
|
2
|
+
require 'purview/columns/boolean'
|
3
|
+
require 'purview/columns/date'
|
4
|
+
require 'purview/columns/float'
|
5
|
+
require 'purview/columns/integer'
|
6
|
+
require 'purview/columns/id'
|
7
|
+
require 'purview/columns/money'
|
8
|
+
require 'purview/columns/string'
|
9
|
+
require 'purview/columns/text'
|
10
|
+
require 'purview/columns/time'
|
11
|
+
require 'purview/columns/timestamp'
|
12
|
+
require 'purview/columns/created_timestamp'
|
13
|
+
require 'purview/columns/updated_timestamp'
|
14
|
+
require 'purview/columns/uuid'
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Purview
|
2
|
+
module Connections
|
3
|
+
class Base
|
4
|
+
def initialize(opts={})
|
5
|
+
@opts = opts
|
6
|
+
end
|
7
|
+
|
8
|
+
def connect
|
9
|
+
@connection ||= new_connection
|
10
|
+
self
|
11
|
+
end
|
12
|
+
|
13
|
+
def disconnect
|
14
|
+
connection.close
|
15
|
+
@connection = nil
|
16
|
+
self
|
17
|
+
end
|
18
|
+
|
19
|
+
def execute(sql)
|
20
|
+
logger.debug("Executing: #{sql}")
|
21
|
+
result = execute_sql(sql)
|
22
|
+
Purview::Structs::Result.new(
|
23
|
+
:rows => extract_rows(result),
|
24
|
+
:rows_affected => extract_rows_affected(result)
|
25
|
+
)
|
26
|
+
end
|
27
|
+
|
28
|
+
def with_transaction
|
29
|
+
raise %{All "#{Base}(s)" must override the "with_transaction" method}
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
include Purview::Mixins::Logger
|
35
|
+
|
36
|
+
attr_reader :opts, :connection
|
37
|
+
|
38
|
+
def execute_sql(sql)
|
39
|
+
raise %{All "#{Base}(s)" must override the "execute_sql" method}
|
40
|
+
end
|
41
|
+
|
42
|
+
def extract_rows(result)
|
43
|
+
raise %{All "#{Base}(s)" must override the "extract_rows" method}
|
44
|
+
end
|
45
|
+
|
46
|
+
def extract_rows_affected(result)
|
47
|
+
raise %{All "#{Base}(s)" must override the "extract_rows_affected" method}
|
48
|
+
end
|
49
|
+
|
50
|
+
def new_connection
|
51
|
+
raise %{All "#{Base}(s)" must override the "new_connection" method}
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Purview
|
2
|
+
module Connections
|
3
|
+
class MySQL < Base
|
4
|
+
def with_transaction
|
5
|
+
connection.query(BEGIN_TRANSACTION)
|
6
|
+
yield.tap { |result| connection.query(COMMIT_TRANSACTION) }
|
7
|
+
rescue Mysql2::Error
|
8
|
+
connection.query(ROLLBACK_TRANSACTION)
|
9
|
+
raise
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
BEGIN_TRANSACTION = 'BEGIN'
|
15
|
+
COMMIT_TRANSACTION = 'COMMIT'
|
16
|
+
ROLLBACK_TRANSACTION = 'ROLLBACK'
|
17
|
+
|
18
|
+
def execute_sql(sql)
|
19
|
+
connection.query(sql, query_opts)
|
20
|
+
end
|
21
|
+
|
22
|
+
def extract_rows(result)
|
23
|
+
result && result.to_a
|
24
|
+
end
|
25
|
+
|
26
|
+
def extract_rows_affected(result)
|
27
|
+
connection.affected_rows
|
28
|
+
end
|
29
|
+
|
30
|
+
def new_connection
|
31
|
+
Mysql2::Client.new(opts)
|
32
|
+
end
|
33
|
+
|
34
|
+
def query_opts
|
35
|
+
{ :cast => false }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|