starproxima_library 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +9 -0
  3. data/Gemfile.lock +69 -0
  4. data/diagrams/add_uthor_seq.png +0 -0
  5. data/diagrams/add_uthor_seq.puml +42 -0
  6. data/diagrams/author.png +0 -0
  7. data/diagrams/author.puml +118 -0
  8. data/diagrams/core.png +0 -0
  9. data/diagrams/core.puml +103 -0
  10. data/diagrams/delete_author.png +0 -0
  11. data/diagrams/delete_author.puml +25 -0
  12. data/diagrams/edit_author.png +0 -0
  13. data/diagrams/edit_author.puml +48 -0
  14. data/diagrams/er.png +0 -0
  15. data/diagrams/er.puml +30 -0
  16. data/diagrams/publisher.png +0 -0
  17. data/diagrams/publisher.puml +118 -0
  18. data/diagrams/requirements.docx +0 -0
  19. data/diagrams/start.png +0 -0
  20. data/diagrams/start.puml +74 -0
  21. data/lib/author/author_db_data_source.rb +90 -0
  22. data/lib/author/controllers/author_controller.rb +57 -0
  23. data/lib/author/controllers/author_input_form_controller_create.rb +44 -0
  24. data/lib/author/controllers/author_input_form_controller_edit.rb +53 -0
  25. data/lib/author/controllers/author_list_controller.rb +107 -0
  26. data/lib/author/ui/author_input_form.rb +69 -0
  27. data/lib/author/ui/author_list_view.rb +170 -0
  28. data/lib/controllers/tab_students_controller.rb +43 -0
  29. data/lib/data_sources/book_db_data_source.rb +43 -0
  30. data/lib/data_sources/db_client.rb +34 -0
  31. data/lib/db_config/config.yaml +5 -0
  32. data/lib/db_config/library_config.yaml +5 -0
  33. data/lib/db_config/migrations/create_db.sql +3 -0
  34. data/lib/db_config/migrations/create_tables.sql +27 -0
  35. data/lib/db_config/mock_data/mock_data.sql +49 -0
  36. data/lib/logger.rb +27 -0
  37. data/lib/main.rb +6 -0
  38. data/lib/models/author.rb +32 -0
  39. data/lib/models/book.rb +31 -0
  40. data/lib/models/publisher.rb +37 -0
  41. data/lib/models/student.rb +102 -0
  42. data/lib/models/student_base.rb +100 -0
  43. data/lib/models/student_short.rb +50 -0
  44. data/lib/publisher/controllers/publisher_input_form_controller_create.rb +44 -0
  45. data/lib/publisher/controllers/publisher_input_form_controller_edit.rb +52 -0
  46. data/lib/publisher/controllers/publisher_list_controller.rb +99 -0
  47. data/lib/publisher/publisher_db_data_source.rb +63 -0
  48. data/lib/publisher/ui/publisher_input_form.rb +69 -0
  49. data/lib/publisher/ui/publisher_list_view.rb +168 -0
  50. data/lib/repositories/adapters/db_source_adapter.rb +54 -0
  51. data/lib/repositories/adapters/file_source_adapter.rb +37 -0
  52. data/lib/repositories/containers/data_list.rb +74 -0
  53. data/lib/repositories/containers/data_list_student_short.rb +18 -0
  54. data/lib/repositories/containers/data_table.rb +35 -0
  55. data/lib/repositories/data_sources/db_data_source.rb +32 -0
  56. data/lib/repositories/data_sources/file_data_source.rb +75 -0
  57. data/lib/repositories/data_sources/transformers/data_transformer_base.rb +15 -0
  58. data/lib/repositories/data_sources/transformers/data_transformer_json.rb +16 -0
  59. data/lib/repositories/data_sources/transformers/data_transformer_yaml.rb +16 -0
  60. data/lib/repositories/student_repository.rb +32 -0
  61. data/lib/state_holders/list_state_notifier.rb +60 -0
  62. data/lib/views/main_window.rb +32 -0
  63. data/lib/views/tab_students.rb +148 -0
  64. data/starproxima_library.gemspec +15 -0
  65. data/test/author_test.rb +51 -0
  66. data/test/book_test.rb +33 -0
  67. data/test/publisher_test.rb +39 -0
  68. data/test/state_notifier_test.rb +80 -0
  69. metadata +123 -0
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ require './lib/state_holders/list_state_notifier'
4
+ require_relative '../ui/publisher_input_form'
5
+ require_relative 'publisher_input_form_controller_create.rb'
6
+ require_relative 'publisher_input_form_controller_edit'
7
+ require_relative '../publisher_db_data_source'
8
+ require 'win32api'
9
+
10
+ class PublisherListController
11
+
12
+ attr_reader :state_notifier
13
+ def initialize(view)
14
+ @view = view
15
+ @state_notifier = ListStateNotifier.new
16
+ @state_notifier.add_listener(@view)
17
+ @publisher_rep = PublisherDBDataSource.new
18
+
19
+ @sort_columns = %w[PublisherID Name Email]
20
+ @sort_by = @sort_columns.first
21
+
22
+ @email_filter_columns = [nil, true, false]
23
+ @email_filter = @email_filter_columns.first
24
+ end
25
+
26
+
27
+
28
+ def on_view_created
29
+ # begin
30
+ # @student_rep = StudentRepository.new(DBSourceAdapter.new)
31
+ # rescue Mysql2::Error::ConnectionError
32
+ # on_db_conn_error
33
+ # end
34
+ end
35
+
36
+ def show_view
37
+ @view.create.show
38
+ end
39
+
40
+ def show_modal_add
41
+ controller = PublisherInputFormControllerCreate.new(self)
42
+ view = PublisherInputForm.new(controller)
43
+ controller.set_view(view)
44
+ view.create.show
45
+ end
46
+
47
+ def show_modal_edit(current_page, per_page, selected_row)
48
+ # item_num = (current_page - 1) * per_page + selected_row
49
+
50
+ item = @state_notifier.get(selected_row)
51
+
52
+ controller = PublisherInputFormControllerEdit.new(self, item)
53
+ view = PublisherInputForm.new(controller)
54
+ controller.set_view(view)
55
+ view.create.show
56
+ end
57
+
58
+ def delete_selected(current_page, per_page, selected_row)
59
+ begin
60
+ item = @state_notifier.get(selected_row)
61
+ @publisher_rep.delete(item.publisher_id)
62
+ @state_notifier.delete(item)
63
+ rescue
64
+ api = Win32API.new('user32', 'MessageBox', ['L', 'P', 'P', 'L'], 'I')
65
+ api.call(0, "You cannot delete the author because he is associated with some book", "Error", 0)
66
+ end
67
+ end
68
+
69
+ def refresh_data(page, per_page)
70
+ # begin
71
+ # @data_list = @student_rep.paginated_short_students(page, per_page, @data_list)
72
+ # @view.update_student_count(@student_rep.student_count)
73
+ # rescue
74
+ # on_db_conn_error
75
+ # end
76
+ items = @publisher_rep.get_list(per_page, page, @sort_by, 'ASC', @email_filter)
77
+ @state_notifier.set_all(items)
78
+ @view.update_student_count(@publisher_rep.count)
79
+ end
80
+
81
+ def sort(page, per_page, sort_index)
82
+ @sort_by = @sort_columns[sort_index]
83
+ refresh_data(page, per_page)
84
+ end
85
+
86
+ def filter_email(page, per_page, filter_index)
87
+ @email_filter = @email_filter_columns[filter_index]
88
+ refresh_data(page, per_page)
89
+ end
90
+
91
+
92
+ private
93
+
94
+ def on_db_conn_error
95
+ api = Win32API.new('user32', 'MessageBox', ['L', 'P', 'P', 'L'], 'I')
96
+ api.call(0, "No connection to DB", "Error", 0)
97
+ exit(false)
98
+ end
99
+ end
@@ -0,0 +1,63 @@
1
+ require 'mysql2'
2
+ require_relative '../data_sources/db_client'
3
+ require_relative '../models/publisher'
4
+
5
+ class PublisherDBDataSource
6
+ def initialize
7
+ @client = DBClient.instance
8
+ end
9
+
10
+ def add(publisher)
11
+ query = "INSERT INTO Publisher (Name, Email) VALUES ('#{publisher.name}', #{publisher.email.nil? ? 'NULL' : "'#{publisher.email}'"})"
12
+ @client.query(query)
13
+ publisher_id = @client.last_id
14
+ get(publisher_id)
15
+ end
16
+
17
+ def change(publisher)
18
+ query = "UPDATE Publisher SET Name='#{publisher.name}', Email=#{publisher.email.nil? ? 'NULL' : "'#{publisher.email}'"} WHERE PublisherID=#{publisher.publisher_id}"
19
+ @client.query(query)
20
+ get(publisher.publisher_id)
21
+ end
22
+
23
+ def delete(id)
24
+ query = "DELETE FROM Publisher WHERE PublisherID=#{id}"
25
+ @client.query(query)
26
+ end
27
+
28
+ def get(id)
29
+ query = "SELECT * FROM Publisher WHERE PublisherID=#{id}"
30
+ result = @client.query(query).first
31
+ if result
32
+ Publisher.new(result[:'PublisherID'], result[:'Name'], result[:'Email'])
33
+ else
34
+ nil
35
+ end
36
+ end
37
+
38
+ def get_list(page_size, page_num, sort_field, sort_direction, has_email = nil)
39
+ offset = (page_num - 1) * page_size
40
+ query = "SELECT * FROM Publisher"
41
+
42
+ if has_email == true
43
+ query += " WHERE Email IS NOT NULL"
44
+ elsif has_email == false
45
+ query += " WHERE Email IS NULL"
46
+ end
47
+
48
+ query += " ORDER BY #{sort_field} #{sort_direction} LIMIT #{page_size} OFFSET #{offset}"
49
+
50
+ results = @client.query(query)
51
+ publishers = []
52
+ results.each do |result|
53
+ publishers << Publisher.new(result[:'PublisherID'], result[:'Name'], result[:'Email'])
54
+ end
55
+ publishers
56
+ end
57
+
58
+ def count
59
+ query = "SELECT COUNT(*) FROM Publisher"
60
+ result = @client.query(query).first
61
+ result[:'COUNT(*)']
62
+ end
63
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'glimmer-dsl-libui'
4
+ require_relative '../controllers/publisher_input_form_controller_create.rb'
5
+ require './lib/models/author'
6
+ require 'win32api'
7
+
8
+ class PublisherInputForm
9
+ include Glimmer
10
+
11
+ def initialize(controller, existing_student = nil)
12
+ @item = existing_student.to_hash unless existing_student.nil?
13
+ @controller = controller
14
+ @entries = {}
15
+ end
16
+
17
+ def on_create
18
+ @controller.on_view_created
19
+ end
20
+
21
+ def create
22
+ @root_container = window('Издатель', 300, 70) {
23
+ resizable false
24
+
25
+ vertical_box {
26
+ @student_form = form {
27
+ stretchy false
28
+
29
+ fields = [[:name, 'Название'], [:email, 'Почта']]
30
+
31
+ fields.each do |field|
32
+ @entries[field[0]] = entry {
33
+ label field[1]
34
+ }
35
+ end
36
+ }
37
+
38
+ button('Сохранить') {
39
+ stretchy false
40
+
41
+ on_clicked {
42
+ values = @entries.transform_values { |v| v.text.force_encoding("utf-8").strip }
43
+ values.transform_values! { |v| v.empty? ? nil : v}
44
+
45
+ @controller.process_fields(values)
46
+ }
47
+ }
48
+ }
49
+ }
50
+ on_create
51
+ @root_container
52
+ end
53
+
54
+ def set_value(field, value)
55
+ return unless @entries.include?(field)
56
+
57
+ @entries[field].text = value
58
+ end
59
+
60
+ def make_readonly(*fields)
61
+ fields.each do |field|
62
+ @entries[field].read_only = true
63
+ end
64
+ end
65
+
66
+ def close
67
+ @root_container.destroy
68
+ end
69
+ end
@@ -0,0 +1,168 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'glimmer-dsl-libui'
4
+ require_relative '../controllers/publisher_list_controller'
5
+ require_relative 'publisher_input_form'
6
+
7
+ class PublisherListView
8
+ include Glimmer
9
+
10
+ PAGE_SIZE = 20
11
+
12
+ def initialize
13
+ @controller = PublisherListController.new(self)
14
+ @current_page = 1
15
+ @total_count = 0
16
+ end
17
+
18
+ def on_create
19
+ @controller.on_view_created
20
+ @controller.refresh_data(@current_page, PAGE_SIZE)
21
+ end
22
+
23
+ # Метод наблюдателя datalist
24
+ # def on_datalist_changed(new_table)
25
+ # arr = new_table.to_2d_array
26
+ # arr.map do |row|
27
+ # row[3] = [row[3][:value], contact_color(row[3][:type])] unless row[3].nil?
28
+ # end
29
+ # @table.model_array = arr
30
+ # end
31
+
32
+ def update(publishers)
33
+ @items = []
34
+
35
+ i = 0
36
+ item_num = 0
37
+ publishers.each do |publisher|
38
+ i += 1
39
+ item_num = ((@current_page - 1) * PAGE_SIZE) + i
40
+ @items << Struct.new(:№, :id, :название, :почта).new(item_num, publisher.publisher_id, publisher.name, publisher.email)
41
+ end
42
+
43
+ @table.model_array = @items
44
+ @page_label.text = "#{@current_page} / #{(@total_count / PAGE_SIZE.to_f).ceil}"
45
+ end
46
+
47
+ def update_student_count(new_cnt)
48
+ @total_count = new_cnt
49
+ @page_label.text = "#{@current_page} / #{(@total_count / PAGE_SIZE.to_f).ceil}"
50
+ end
51
+
52
+ def create
53
+
54
+ root_container = horizontal_box {
55
+ # Секция 1
56
+ vertical_box {
57
+ stretchy false
58
+
59
+ vertical_box {
60
+ stretchy false
61
+
62
+ label {
63
+ text 'Почта'
64
+ }
65
+ combobox { |c|
66
+ items ['Не важно','Есть','Нет']
67
+ selected 0
68
+ on_selected do
69
+ @controller.filter_email(@current_page, PAGE_SIZE, c.selected)
70
+ end
71
+ }
72
+
73
+ label {
74
+ text 'Сортировка'
75
+ }
76
+ combobox { |c|
77
+ items ['ID','Название','Почта']
78
+ selected 0
79
+ on_selected do
80
+ @controller.sort(@current_page, PAGE_SIZE, c.selected)
81
+ end
82
+ }
83
+ }
84
+
85
+
86
+ }
87
+
88
+ # Секция 2
89
+ vertical_box {
90
+ @table = refined_table(
91
+ table_editable: false,
92
+ filter: lambda do |row_hash, query|
93
+ utf8_query = query.force_encoding("utf-8")
94
+ row_hash['Имя автора'].include?(utf8_query)
95
+ end,
96
+ table_columns: {
97
+ '№' => :text,
98
+ 'ID' => :text,
99
+ 'Название' => :text,
100
+ 'Почта' => :text,
101
+ },
102
+ per_page: PAGE_SIZE,
103
+
104
+ )
105
+
106
+ @pages = horizontal_box {
107
+ stretchy false
108
+
109
+ button("<") {
110
+ stretchy true
111
+
112
+ on_clicked do
113
+ @current_page = [@current_page - 1, 1].max
114
+ @controller.refresh_data(@current_page, PAGE_SIZE)
115
+ end
116
+
117
+ }
118
+ @page_label = label("...") { stretchy false }
119
+ button(">") {
120
+ stretchy true
121
+
122
+ on_clicked do
123
+ @current_page = [@current_page + 1, (@total_count / PAGE_SIZE.to_f).ceil].min
124
+ @controller.refresh_data(@current_page, PAGE_SIZE)
125
+ end
126
+ }
127
+ }
128
+ }
129
+
130
+ # Секция 3
131
+ vertical_box {
132
+ stretchy false
133
+
134
+ button('Добавить') {
135
+ stretchy false
136
+
137
+ on_clicked {
138
+ @controller.show_modal_add
139
+ }
140
+ }
141
+ button('Изменить') {
142
+ stretchy false
143
+
144
+ on_clicked {
145
+ @controller.show_modal_edit(@current_page, PAGE_SIZE, @table.selection) unless @table.selection.nil?
146
+ }
147
+ }
148
+ button('Удалить') {
149
+ stretchy false
150
+
151
+ on_clicked {
152
+ @controller.delete_selected(@current_page, PAGE_SIZE, @table.selection) unless @table.selection.nil?
153
+ @controller.refresh_data(@current_page, PAGE_SIZE)
154
+ }
155
+ }
156
+ button('Обновить') {
157
+ stretchy false
158
+
159
+ on_clicked {
160
+ @controller.refresh_data(@current_page, PAGE_SIZE)
161
+ }
162
+ }
163
+ }
164
+ }
165
+ on_create
166
+ root_container
167
+ end
168
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require './lib/repositories/data_sources/db_data_source'
4
+ require './lib/models/student'
5
+ require './lib/models/student_short'
6
+ require './lib/repositories/containers/data_list_student_short'
7
+
8
+ class DBSourceAdapter
9
+ def initialize
10
+ @db = DBDataSource.instance
11
+ end
12
+
13
+ def student_by_id(student_id)
14
+ hash = @db.prepare_exec('SELECT * FROM student WHERE id = ?', student_id).first
15
+ return nil if hash.nil?
16
+
17
+ Student.from_hash(hash)
18
+ end
19
+
20
+ def paginated_short_students(page, count, existing_data_list = nil)
21
+ offset = (page - 1) * count
22
+ students = @db.prepare_exec('SELECT * FROM student LIMIT ?, ?', offset, count)
23
+ slice = students.map { |h| StudentShort.from_student(Student.from_hash(h)) }
24
+ return DataListStudentShort.new(slice) if existing_data_list.nil?
25
+
26
+ existing_data_list.replace_objects(slice)
27
+ existing_data_list
28
+ end
29
+
30
+ def add_student(student)
31
+ template = 'INSERT INTO student(last_name, first_name, father_name, phone, telegram, email, git) VALUES (?, ?, ?, ?, ?, ?, ?)'
32
+ @db.prepare_exec(template, *student_fields(student))
33
+ @db.query('SELECT LAST_INSERT_ID()').first.first[1]
34
+ end
35
+
36
+ def replace_student(student_id, student)
37
+ template = 'UPDATE student SET last_name=?, first_name=?, father_name=?, phone=?, telegram=?, email=?, git=? WHERE id=?'
38
+ @db.prepare_exec(template, *student_fields(student), student_id)
39
+ end
40
+
41
+ def remove_student(student_id)
42
+ @db.prepare_exec('DELETE FROM student WHERE id = ?', student_id)
43
+ end
44
+
45
+ def student_count
46
+ @db.query('SELECT COUNT(id) FROM student').first.first[1]
47
+ end
48
+
49
+ private
50
+
51
+ def student_fields(student)
52
+ [student.last_name, student.first_name, student.father_name, student.phone, student.telegram, student.email, student.git]
53
+ end
54
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ class FileSourceAdapter
4
+ def initialize(data_transformer, file_path)
5
+ @file_path = file_path
6
+ @file_source = FileDataSource.new(data_transformer)
7
+ @file_source.load_from_file(file_path)
8
+ end
9
+
10
+ def student_by_id(student_id)
11
+ @file_source.student_by_id(student_id)
12
+ end
13
+
14
+ def paginated_short_students(page, count, existing_data_list = nil)
15
+ @file_source.paginated_short_students(page, count, existing_data_list)
16
+ end
17
+
18
+ def add_student(student)
19
+ added_id = @file_source.add_student(student)
20
+ @file_source.save_to_file(@file_path)
21
+ added_id
22
+ end
23
+
24
+ def replace_student(student_id, student)
25
+ @file_source.replace_student(student_id, student)
26
+ @file_source.save_to_file(@file_path)
27
+ end
28
+
29
+ def remove_student(student_id)
30
+ @file_source.remove_student(student_id)
31
+ @file_source.save_to_file(@file_path)
32
+ end
33
+
34
+ def student_count
35
+ @file_source.student_count
36
+ end
37
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require './lib/repositories/containers/data_table'
4
+
5
+ class DataList
6
+ # Это "абстрактный" класс
7
+ private_class_method :new
8
+
9
+ attr_writer :objects
10
+
11
+ # Конструктор, принимает массив любых объектов
12
+ def initialize(objects)
13
+ self.objects = objects
14
+ @listeners = []
15
+ end
16
+
17
+ def add_listener(listener)
18
+ @listeners << listener
19
+ end
20
+
21
+ def remove_listener(listener)
22
+ @listeners.delete(listener)
23
+ end
24
+
25
+ def notify
26
+ @listeners.each { |lst| lst.on_datalist_changed(data_table) }
27
+ end
28
+
29
+ # Выбрать элемент по номеру
30
+ def select_element(number)
31
+ self.selected_num = number < objects.size ? number : nil
32
+ end
33
+
34
+ def selected_id
35
+ objects[selected_num].id
36
+ end
37
+
38
+ # Получить DataTable со всеми элементами.
39
+ def data_table
40
+ result = []
41
+ counter = 0
42
+ objects.each do |obj|
43
+ row = []
44
+ row << counter
45
+ row.push(*table_fields(obj))
46
+ result << row
47
+ counter += 1
48
+ end
49
+ DataTable.new(result)
50
+ end
51
+
52
+ # Добавить элементы в конец списка
53
+ def replace_objects(objects)
54
+ self.objects = objects.dup
55
+ notify
56
+ end
57
+
58
+ protected
59
+
60
+ # Список значений полей для DataTable. Переопределить в наследниках
61
+ def table_fields(_obj)
62
+ []
63
+ end
64
+
65
+ # Имена атрибутов объектов по порядку. Переопределить в наследниках
66
+ def column_names
67
+ []
68
+ end
69
+
70
+ private
71
+
72
+ attr_reader :objects
73
+ attr_accessor :selected_num
74
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'data_list'
4
+
5
+ class DataListStudentShort < DataList
6
+ # Делаем приватный new предка публичным
7
+ public_class_method :new
8
+
9
+ def column_names
10
+ ['Фамилия И. О.', 'Гит', 'Контакт']
11
+ end
12
+
13
+ protected
14
+
15
+ def table_fields(obj)
16
+ [obj.last_name_and_initials, obj.git, obj.contact]
17
+ end
18
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ class DataTable
4
+ attr_reader :rows_count, :cols_count
5
+
6
+ # Конструктор, принимает 2D Array
7
+ def initialize(table)
8
+ self.rows_count = table.size
9
+ max_cols = 0
10
+ table.each { |row| max_cols = row.size if row.size > max_cols }
11
+ self.cols_count = max_cols
12
+ self.table = table
13
+ end
14
+
15
+ # Получить значение в ячейке [row, col]
16
+ def get_item(row, col)
17
+ return nil if row >= rows_count
18
+ return nil if col >= cols_count
19
+
20
+ table[row][col].dup
21
+ end
22
+
23
+ def to_2d_array
24
+ table.dup
25
+ end
26
+
27
+ def to_s
28
+ "DataTable (#{rows_count}x#{cols_count})"
29
+ end
30
+
31
+ private
32
+
33
+ attr_accessor :table
34
+ attr_writer :rows_count, :cols_count
35
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'mysql2'
4
+
5
+ class DBDataSource
6
+ private_class_method :new
7
+ @instance_mutex = Mutex.new
8
+
9
+ def initialize
10
+ db_config = YAML.load_file('./lib/db_config/config.yaml').transform_keys(&:to_sym)
11
+ @client = Mysql2::Client.new(db_config)
12
+ @client.query_options.merge!(symbolize_keys: true)
13
+ end
14
+
15
+ def self.instance
16
+ return @instance if @instance
17
+
18
+ @instance_mutex.synchronize do
19
+ @instance ||= new
20
+ end
21
+
22
+ @instance
23
+ end
24
+
25
+ def prepare_exec(statement, *params)
26
+ @client.prepare(statement).execute(*params)
27
+ end
28
+
29
+ def query(statement)
30
+ @client.query(statement)
31
+ end
32
+ end