jackcess-rb 0.1.0-java
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/CHANGELOG.md +18 -0
- data/LICENSE +21 -0
- data/README.md +435 -0
- data/lib/jackcess/column.rb +61 -0
- data/lib/jackcess/database.rb +440 -0
- data/lib/jackcess/errors.rb +9 -0
- data/lib/jackcess/index.rb +89 -0
- data/lib/jackcess/row.rb +173 -0
- data/lib/jackcess/table.rb +191 -0
- data/lib/jackcess/type_converter.rb +130 -0
- data/lib/jackcess/version.rb +11 -0
- data/lib/jackcess.rb +35 -0
- data/vendor/commons-lang3-3.13.0.jar +0 -0
- data/vendor/commons-logging-1.2.jar +0 -0
- data/vendor/jackcess-4.0.4.jar +0 -0
- metadata +63 -0
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jackcess
|
|
4
|
+
# Represents a Microsoft Access database file (.mdb or .accdb).
|
|
5
|
+
#
|
|
6
|
+
# This class provides the main entry point for interacting with Access databases
|
|
7
|
+
# through the Jackcess library. It supports opening existing databases, creating
|
|
8
|
+
# new ones, and managing tables within the database.
|
|
9
|
+
#
|
|
10
|
+
# @example Open an existing database
|
|
11
|
+
# db = Jackcess::Database.open('path/to/database.accdb')
|
|
12
|
+
# db.table_names
|
|
13
|
+
# # => ["Customers", "Orders", "Products"]
|
|
14
|
+
# db.close
|
|
15
|
+
#
|
|
16
|
+
# @example Create a new database with block
|
|
17
|
+
# Jackcess::Database.create('new.accdb', :v2007) do |db|
|
|
18
|
+
# db.create_table('Users') do |t|
|
|
19
|
+
# t.column 'ID', :long, auto_number: true
|
|
20
|
+
# t.column 'Name', :text, length: 255
|
|
21
|
+
# t.primary_key 'ID'
|
|
22
|
+
# end
|
|
23
|
+
# end # Database automatically closed
|
|
24
|
+
class Database
|
|
25
|
+
# The underlying Java database object from Jackcess
|
|
26
|
+
# @return [Java::ComHealthmarketscienceJackcess::Database]
|
|
27
|
+
attr_reader :java_database
|
|
28
|
+
|
|
29
|
+
# Creates a new Database instance wrapping a Java database object.
|
|
30
|
+
# This method is typically not called directly; use {.open} or {.create} instead.
|
|
31
|
+
#
|
|
32
|
+
# @param java_database [Java::ComHealthmarketscienceJackcess::Database] The Java database object
|
|
33
|
+
# @api private
|
|
34
|
+
def initialize(java_database)
|
|
35
|
+
@java_database = java_database
|
|
36
|
+
@closed = false
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Opens an existing Access database file.
|
|
40
|
+
#
|
|
41
|
+
# This method opens an existing Microsoft Access database file (.mdb or .accdb)
|
|
42
|
+
# for reading and/or writing. If a block is given, the database will be
|
|
43
|
+
# automatically closed when the block exits, even if an exception is raised.
|
|
44
|
+
#
|
|
45
|
+
# @param path [String] The file path to the database
|
|
46
|
+
# @param options [Hash] Optional configuration
|
|
47
|
+
# @option options [Boolean] :readonly (false) Open the database in read-only mode
|
|
48
|
+
# @option options [Boolean] :auto_sync (true) Automatically sync changes to disk
|
|
49
|
+
#
|
|
50
|
+
# @return [Database] A new Database instance (without block)
|
|
51
|
+
# @yield [db] Passes the database to the block
|
|
52
|
+
# @yieldparam db [Database] The opened database
|
|
53
|
+
# @yieldreturn [Object] The return value of the block (with block form)
|
|
54
|
+
#
|
|
55
|
+
# @raise [ArgumentError] If path is nil, empty, or not a String
|
|
56
|
+
# @raise [DatabaseError] If the file doesn't exist, is a directory, or cannot be opened
|
|
57
|
+
#
|
|
58
|
+
# @example Open database without block
|
|
59
|
+
# db = Jackcess::Database.open('data/northwind.accdb')
|
|
60
|
+
# # ... work with database ...
|
|
61
|
+
# db.close
|
|
62
|
+
#
|
|
63
|
+
# @example Open database with block (auto-close)
|
|
64
|
+
# Jackcess::Database.open('data/northwind.accdb') do |db|
|
|
65
|
+
# puts db.table_names
|
|
66
|
+
# end # Database automatically closed
|
|
67
|
+
#
|
|
68
|
+
# @example Open in read-only mode
|
|
69
|
+
# db = Jackcess::Database.open('data/northwind.accdb', readonly: true)
|
|
70
|
+
def self.open(path, options = {})
|
|
71
|
+
raise ArgumentError, "Database path cannot be nil" if path.nil?
|
|
72
|
+
raise ArgumentError, "Database path must be a String" unless path.is_a?(String)
|
|
73
|
+
raise ArgumentError, "Database path cannot be empty" if path.empty?
|
|
74
|
+
|
|
75
|
+
path = File.expand_path(path)
|
|
76
|
+
raise DatabaseError, "Database file not found: #{path}" unless File.exist?(path)
|
|
77
|
+
raise DatabaseError, "Path is a directory, not a file: #{path}" if File.directory?(path)
|
|
78
|
+
|
|
79
|
+
begin
|
|
80
|
+
builder = DatabaseBuilder.new(java.io.File.new(path))
|
|
81
|
+
builder.set_read_only(options[:readonly]) if options.key?(:readonly)
|
|
82
|
+
builder.set_auto_sync(options[:auto_sync]) if options.key?(:auto_sync)
|
|
83
|
+
|
|
84
|
+
java_db = builder.open
|
|
85
|
+
db = new(java_db)
|
|
86
|
+
|
|
87
|
+
if block_given?
|
|
88
|
+
begin
|
|
89
|
+
yield db
|
|
90
|
+
ensure
|
|
91
|
+
db.close
|
|
92
|
+
end
|
|
93
|
+
else
|
|
94
|
+
db
|
|
95
|
+
end
|
|
96
|
+
rescue Java::JavaIo::IOException => e
|
|
97
|
+
raise DatabaseError, "Failed to open database '#{path}': #{e.message}"
|
|
98
|
+
rescue Java::JavaLang::Exception => e
|
|
99
|
+
raise DatabaseError, "Failed to open database '#{path}': #{e.message}"
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Creates a new Access database file.
|
|
104
|
+
#
|
|
105
|
+
# This method creates a new Microsoft Access database file in the specified format.
|
|
106
|
+
# If a block is given, the database will be automatically closed when the block exits.
|
|
107
|
+
#
|
|
108
|
+
# @param path [String] The file path for the new database
|
|
109
|
+
# @param format [Symbol] The Access file format (:v2000, :v2003, :v2007, or :v2010)
|
|
110
|
+
#
|
|
111
|
+
# @return [Database] A new Database instance (without block)
|
|
112
|
+
# @yield [db] Passes the database to the block
|
|
113
|
+
# @yieldparam db [Database] The created database
|
|
114
|
+
# @yieldreturn [Object] The return value of the block (with block form)
|
|
115
|
+
#
|
|
116
|
+
# @raise [ArgumentError] If path is nil, empty, not a String, or format is invalid
|
|
117
|
+
# @raise [DatabaseError] If the database cannot be created
|
|
118
|
+
#
|
|
119
|
+
# @example Create a database without block
|
|
120
|
+
# db = Jackcess::Database.create('new.accdb', :v2007)
|
|
121
|
+
# # ... work with database ...
|
|
122
|
+
# db.close
|
|
123
|
+
#
|
|
124
|
+
# @example Create a database with block (auto-close)
|
|
125
|
+
# Jackcess::Database.create('new.accdb', :v2007) do |db|
|
|
126
|
+
# db.create_table('Products') do |t|
|
|
127
|
+
# t.column 'ID', :long, auto_number: true
|
|
128
|
+
# t.column 'Name', :text, length: 255
|
|
129
|
+
# t.primary_key 'ID'
|
|
130
|
+
# end
|
|
131
|
+
# end # Database automatically closed
|
|
132
|
+
def self.create(path, format = :v2000)
|
|
133
|
+
raise ArgumentError, "Database path cannot be nil" if path.nil?
|
|
134
|
+
raise ArgumentError, "Database path must be a String" unless path.is_a?(String)
|
|
135
|
+
raise ArgumentError, "Database path cannot be empty" if path.empty?
|
|
136
|
+
raise ArgumentError, "Database format cannot be nil" if format.nil?
|
|
137
|
+
|
|
138
|
+
path = File.expand_path(path)
|
|
139
|
+
|
|
140
|
+
begin
|
|
141
|
+
file_format = case format
|
|
142
|
+
when :v2000
|
|
143
|
+
com.healthmarketscience.jackcess.Database::FileFormat::V2000
|
|
144
|
+
when :v2003
|
|
145
|
+
com.healthmarketscience.jackcess.Database::FileFormat::V2003
|
|
146
|
+
when :v2007
|
|
147
|
+
com.healthmarketscience.jackcess.Database::FileFormat::V2007
|
|
148
|
+
when :v2010
|
|
149
|
+
com.healthmarketscience.jackcess.Database::FileFormat::V2010
|
|
150
|
+
else
|
|
151
|
+
raise DatabaseError, "Unknown database format: #{format}. Valid formats are: :v2000, :v2003, :v2007, :v2010"
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
java_db = DatabaseBuilder.create(file_format, java.io.File.new(path))
|
|
155
|
+
db = new(java_db)
|
|
156
|
+
|
|
157
|
+
if block_given?
|
|
158
|
+
begin
|
|
159
|
+
yield db
|
|
160
|
+
ensure
|
|
161
|
+
db.close
|
|
162
|
+
end
|
|
163
|
+
else
|
|
164
|
+
db
|
|
165
|
+
end
|
|
166
|
+
rescue Java::JavaIo::IOException => e
|
|
167
|
+
raise DatabaseError, "Failed to create database '#{path}': #{e.message}"
|
|
168
|
+
rescue Java::JavaLang::Exception => e
|
|
169
|
+
raise DatabaseError, "Failed to create database '#{path}': #{e.message}"
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Retrieves a table by name from the database.
|
|
174
|
+
#
|
|
175
|
+
# @param name [String, Symbol] The name of the table to retrieve
|
|
176
|
+
#
|
|
177
|
+
# @return [Table] The requested table
|
|
178
|
+
#
|
|
179
|
+
# @raise [ArgumentError] If name is nil or not a String/Symbol
|
|
180
|
+
# @raise [TableNotFoundError] If the table doesn't exist
|
|
181
|
+
# @raise [DatabaseError] If the database is closed or table cannot be accessed
|
|
182
|
+
#
|
|
183
|
+
# @example Get a table
|
|
184
|
+
# table = db.table('Customers')
|
|
185
|
+
# puts table.name # => "Customers"
|
|
186
|
+
def table(name)
|
|
187
|
+
check_closed!
|
|
188
|
+
raise ArgumentError, "Table name cannot be nil" if name.nil?
|
|
189
|
+
raise ArgumentError, "Table name must be a String or Symbol" unless name.is_a?(String) || name.is_a?(Symbol)
|
|
190
|
+
|
|
191
|
+
name = name.to_s
|
|
192
|
+
begin
|
|
193
|
+
java_table = @java_database.get_table(name)
|
|
194
|
+
raise TableNotFoundError, "Table not found: #{name}" if java_table.nil?
|
|
195
|
+
|
|
196
|
+
Table.new(java_table, self)
|
|
197
|
+
rescue Java::JavaIo::IOException => e
|
|
198
|
+
raise DatabaseError, "Failed to access table '#{name}': #{e.message}"
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# Returns a sorted array of all table names in the database.
|
|
203
|
+
#
|
|
204
|
+
# @return [Array<String>] Sorted array of table names
|
|
205
|
+
#
|
|
206
|
+
# @raise [DatabaseError] If the database is closed or table names cannot be retrieved
|
|
207
|
+
#
|
|
208
|
+
# @example List all tables
|
|
209
|
+
# db.table_names
|
|
210
|
+
# # => ["Customers", "Orders", "Products"]
|
|
211
|
+
def table_names
|
|
212
|
+
check_closed!
|
|
213
|
+
|
|
214
|
+
begin
|
|
215
|
+
@java_database.get_table_names.to_a.sort
|
|
216
|
+
rescue Java::JavaIo::IOException => e
|
|
217
|
+
raise DatabaseError, "Failed to get table names: #{e.message}"
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Creates a new table in the database.
|
|
222
|
+
#
|
|
223
|
+
# This method uses a DSL (Domain-Specific Language) for defining the table schema.
|
|
224
|
+
# The block receives a table builder object that supports `column` and `primary_key` methods.
|
|
225
|
+
#
|
|
226
|
+
# @param name [String, Symbol] The name for the new table
|
|
227
|
+
#
|
|
228
|
+
# @return [Table] The newly created table
|
|
229
|
+
#
|
|
230
|
+
# @yield [t] Passes a table builder to the block for defining columns
|
|
231
|
+
# @yieldparam t [TableBuilderDSL] The table builder
|
|
232
|
+
#
|
|
233
|
+
# @raise [ArgumentError] If name is nil, empty, or not a String/Symbol
|
|
234
|
+
# @raise [DatabaseError] If the database is closed or table cannot be created
|
|
235
|
+
#
|
|
236
|
+
# @example Create a simple table
|
|
237
|
+
# table = db.create_table('Users') do |t|
|
|
238
|
+
# t.column 'ID', :long, auto_number: true
|
|
239
|
+
# t.column 'Name', :text, length: 255
|
|
240
|
+
# t.column 'Email', :text, length: 255
|
|
241
|
+
# t.column 'CreatedAt', :date_time
|
|
242
|
+
# t.primary_key 'ID'
|
|
243
|
+
# end
|
|
244
|
+
#
|
|
245
|
+
# @see TableBuilderDSL
|
|
246
|
+
def create_table(name, &block)
|
|
247
|
+
check_closed!
|
|
248
|
+
raise ArgumentError, "Table name cannot be nil" if name.nil?
|
|
249
|
+
raise ArgumentError, "Table name must be a String or Symbol" unless name.is_a?(String) || name.is_a?(Symbol)
|
|
250
|
+
raise ArgumentError, "Table name cannot be empty" if name.to_s.empty?
|
|
251
|
+
|
|
252
|
+
begin
|
|
253
|
+
builder = TableBuilder.new(name)
|
|
254
|
+
table_builder = TableBuilderDSL.new(builder)
|
|
255
|
+
table_builder.instance_eval(&block) if block_given?
|
|
256
|
+
|
|
257
|
+
java_table = builder.to_table(@java_database)
|
|
258
|
+
Table.new(java_table, self)
|
|
259
|
+
rescue Java::JavaIo::IOException => e
|
|
260
|
+
raise DatabaseError, "Failed to create table '#{name}': #{e.message}"
|
|
261
|
+
rescue Java::JavaLang::Exception => e
|
|
262
|
+
raise DatabaseError, "Failed to create table '#{name}': #{e.message}"
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
# Executes a block within a transaction context.
|
|
267
|
+
#
|
|
268
|
+
# Note: Jackcess doesn't have explicit ACID transaction support like traditional
|
|
269
|
+
# databases. This method provides a semantic grouping for operations and will
|
|
270
|
+
# wrap exceptions in DatabaseError. For true atomicity, consider implementing
|
|
271
|
+
# checkpointing or using database-level features if available.
|
|
272
|
+
#
|
|
273
|
+
# @yield The block of operations to execute
|
|
274
|
+
#
|
|
275
|
+
# @raise [DatabaseError] If the database is closed or if an error occurs during the transaction
|
|
276
|
+
#
|
|
277
|
+
# @example Execute a transaction
|
|
278
|
+
# db.transaction do
|
|
279
|
+
# table = db.table('Accounts')
|
|
280
|
+
# # Perform multiple operations
|
|
281
|
+
# end
|
|
282
|
+
def transaction
|
|
283
|
+
check_closed!
|
|
284
|
+
|
|
285
|
+
# Note: Jackcess doesn't have explicit transaction support like traditional databases
|
|
286
|
+
# This is a placeholder for future enhancement
|
|
287
|
+
yield
|
|
288
|
+
rescue StandardError => e
|
|
289
|
+
raise DatabaseError, "Transaction failed: #{e.message}"
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
# Closes the database and flushes any pending changes to disk.
|
|
293
|
+
#
|
|
294
|
+
# After calling this method, the database cannot be used. Calling close on an
|
|
295
|
+
# already-closed database is safe and will not raise an error.
|
|
296
|
+
#
|
|
297
|
+
# @return [nil]
|
|
298
|
+
#
|
|
299
|
+
# @raise [DatabaseError] If an error occurs during the close operation
|
|
300
|
+
#
|
|
301
|
+
# @example Close a database
|
|
302
|
+
# db = Jackcess::Database.open('data.accdb')
|
|
303
|
+
# # ... work with database ...
|
|
304
|
+
# db.close
|
|
305
|
+
def close
|
|
306
|
+
return if @closed
|
|
307
|
+
|
|
308
|
+
begin
|
|
309
|
+
@java_database.close
|
|
310
|
+
@closed = true
|
|
311
|
+
rescue Java::JavaIo::IOException => e
|
|
312
|
+
# Log the error but mark as closed to prevent further operations
|
|
313
|
+
@closed = true
|
|
314
|
+
raise DatabaseError, "Failed to close database cleanly: #{e.message}"
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
# Checks whether the database has been closed.
|
|
319
|
+
#
|
|
320
|
+
# @return [Boolean] true if the database is closed, false otherwise
|
|
321
|
+
#
|
|
322
|
+
# @example Check if database is closed
|
|
323
|
+
# db = Jackcess::Database.open('data.accdb')
|
|
324
|
+
# db.closed? # => false
|
|
325
|
+
# db.close
|
|
326
|
+
# db.closed? # => true
|
|
327
|
+
def closed?
|
|
328
|
+
@closed
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
# Flushes any pending changes to disk.
|
|
332
|
+
#
|
|
333
|
+
# This method forces all buffered changes to be written to the database file.
|
|
334
|
+
# It's generally not necessary to call this manually unless you need to ensure
|
|
335
|
+
# changes are persisted immediately.
|
|
336
|
+
#
|
|
337
|
+
# @return [nil]
|
|
338
|
+
#
|
|
339
|
+
# @raise [DatabaseError] If the database is closed or flush fails
|
|
340
|
+
#
|
|
341
|
+
# @example Flush changes
|
|
342
|
+
# db = Jackcess::Database.open('data.accdb')
|
|
343
|
+
# # ... make changes ...
|
|
344
|
+
# db.flush # Ensure changes are written to disk
|
|
345
|
+
def flush
|
|
346
|
+
check_closed!
|
|
347
|
+
|
|
348
|
+
begin
|
|
349
|
+
@java_database.flush
|
|
350
|
+
rescue Java::JavaIo::IOException => e
|
|
351
|
+
raise DatabaseError, "Failed to flush database: #{e.message}"
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
private
|
|
356
|
+
|
|
357
|
+
def check_closed!
|
|
358
|
+
raise DatabaseError, "Database is closed" if @closed
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
# DSL (Domain-Specific Language) for building table schemas.
|
|
362
|
+
#
|
|
363
|
+
# This class provides a fluent interface for defining table columns and indexes
|
|
364
|
+
# when creating new tables. It's used internally by {Database#create_table}.
|
|
365
|
+
#
|
|
366
|
+
# @api private
|
|
367
|
+
class TableBuilderDSL
|
|
368
|
+
# Creates a new table builder DSL instance.
|
|
369
|
+
#
|
|
370
|
+
# @param builder [Java::ComHealthmarketscienceJackcess::TableBuilder] The underlying Java table builder
|
|
371
|
+
# @api private
|
|
372
|
+
def initialize(builder)
|
|
373
|
+
@builder = builder
|
|
374
|
+
@primary_key_column = nil
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
# Defines a column in the table.
|
|
378
|
+
#
|
|
379
|
+
# @param name [String, Symbol] The column name
|
|
380
|
+
# @param type [Symbol] The column data type (:text, :memo, :byte, :int, :long, :float, :double, :currency, :date_time, :boolean, :binary, :guid)
|
|
381
|
+
# @param options [Hash] Column options
|
|
382
|
+
# @option options [Integer] :length The maximum length for text columns
|
|
383
|
+
# @option options [Boolean] :auto_number (false) Whether this is an auto-incrementing column
|
|
384
|
+
#
|
|
385
|
+
# @return [void]
|
|
386
|
+
#
|
|
387
|
+
# @raise [ArgumentError] If name or type is invalid
|
|
388
|
+
#
|
|
389
|
+
# @example Define columns
|
|
390
|
+
# db.create_table('Users') do |t|
|
|
391
|
+
# t.column 'ID', :long, auto_number: true
|
|
392
|
+
# t.column 'Name', :text, length: 255
|
|
393
|
+
# t.column 'Email', :text, length: 255
|
|
394
|
+
# t.column 'Age', :int
|
|
395
|
+
# t.column 'Balance', :currency
|
|
396
|
+
# t.column 'IsActive', :boolean
|
|
397
|
+
# t.column 'CreatedAt', :date_time
|
|
398
|
+
# end
|
|
399
|
+
def column(name, type, options = {})
|
|
400
|
+
raise ArgumentError, "Column name cannot be nil" if name.nil?
|
|
401
|
+
raise ArgumentError, "Column name must be a String or Symbol" unless name.is_a?(String) || name.is_a?(Symbol)
|
|
402
|
+
raise ArgumentError, "Column name cannot be empty" if name.to_s.empty?
|
|
403
|
+
raise ArgumentError, "Column type cannot be nil" if type.nil?
|
|
404
|
+
|
|
405
|
+
data_type = TypeConverter.ruby_type_to_data_type(type)
|
|
406
|
+
col_builder = ColumnBuilder.new(name, data_type)
|
|
407
|
+
|
|
408
|
+
col_builder.set_length(options[:length]) if options[:length]
|
|
409
|
+
col_builder.set_auto_number(options[:auto_number]) if options[:auto_number]
|
|
410
|
+
|
|
411
|
+
@builder.add_column(col_builder.to_column)
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
# Defines the primary key for the table.
|
|
415
|
+
#
|
|
416
|
+
# @param column_name [String, Symbol] The name of the column to use as primary key
|
|
417
|
+
#
|
|
418
|
+
# @return [void]
|
|
419
|
+
#
|
|
420
|
+
# @raise [ArgumentError] If column_name is nil
|
|
421
|
+
#
|
|
422
|
+
# @example Set primary key
|
|
423
|
+
# db.create_table('Users') do |t|
|
|
424
|
+
# t.column 'ID', :long, auto_number: true
|
|
425
|
+
# t.column 'Name', :text, length: 255
|
|
426
|
+
# t.primary_key 'ID'
|
|
427
|
+
# end
|
|
428
|
+
def primary_key(column_name)
|
|
429
|
+
raise ArgumentError, "Primary key column name cannot be nil" if column_name.nil?
|
|
430
|
+
|
|
431
|
+
index_builder = com.healthmarketscience.jackcess.IndexBuilder.new(
|
|
432
|
+
com.healthmarketscience.jackcess.IndexBuilder::PRIMARY_KEY_NAME
|
|
433
|
+
)
|
|
434
|
+
index_builder.add_columns(column_name)
|
|
435
|
+
index_builder.set_primary_key
|
|
436
|
+
@builder.add_index(index_builder)
|
|
437
|
+
end
|
|
438
|
+
end
|
|
439
|
+
end
|
|
440
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jackcess
|
|
4
|
+
# Represents an index on a table in an Access database.
|
|
5
|
+
#
|
|
6
|
+
# Indexes provide fast lookups and enforce uniqueness constraints.
|
|
7
|
+
# This class wraps Jackcess's Index object to provide a Ruby-friendly interface.
|
|
8
|
+
#
|
|
9
|
+
# @example Inspect table indexes
|
|
10
|
+
# table = db.table('Users')
|
|
11
|
+
# table.indexes.each do |index|
|
|
12
|
+
# puts "#{index.name}: #{index.columns.join(', ')}"
|
|
13
|
+
# puts "Primary key: #{index.primary_key?}"
|
|
14
|
+
# puts "Unique: #{index.unique?}"
|
|
15
|
+
# end
|
|
16
|
+
class Index
|
|
17
|
+
# The underlying Java index object from Jackcess
|
|
18
|
+
# @return [Java::ComHealthmarketscienceJackcess::Index]
|
|
19
|
+
attr_reader :java_index
|
|
20
|
+
|
|
21
|
+
# Creates a new Index instance wrapping a Java index object.
|
|
22
|
+
#
|
|
23
|
+
# @param java_index [Java::ComHealthmarketscienceJackcess::Index] The Java index object
|
|
24
|
+
# @api private
|
|
25
|
+
def initialize(java_index)
|
|
26
|
+
@java_index = java_index
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Returns the name of the index.
|
|
30
|
+
#
|
|
31
|
+
# @return [String] The index name
|
|
32
|
+
#
|
|
33
|
+
# @example Get index name
|
|
34
|
+
# index.name # => "PrimaryKey"
|
|
35
|
+
def name
|
|
36
|
+
@java_index.get_name
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Returns an array of column names that comprise this index.
|
|
40
|
+
#
|
|
41
|
+
# @return [Array<String>] Array of column names in the index
|
|
42
|
+
#
|
|
43
|
+
# @example Get index columns
|
|
44
|
+
# index.columns # => ["UserID", "Email"]
|
|
45
|
+
def columns
|
|
46
|
+
@java_index.get_columns.map(&:get_name)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Checks whether this index is the table's primary key.
|
|
50
|
+
#
|
|
51
|
+
# @return [Boolean] true if this is a primary key index, false otherwise
|
|
52
|
+
#
|
|
53
|
+
# @example Check if primary key
|
|
54
|
+
# index.primary_key? # => true
|
|
55
|
+
def primary_key?
|
|
56
|
+
@java_index.is_primary_key
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Checks whether this index enforces uniqueness.
|
|
60
|
+
#
|
|
61
|
+
# @return [Boolean] true if values in this index must be unique, false otherwise
|
|
62
|
+
#
|
|
63
|
+
# @example Check if unique
|
|
64
|
+
# index.unique? # => true
|
|
65
|
+
def unique?
|
|
66
|
+
@java_index.is_unique
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Checks whether this index ignores null values.
|
|
70
|
+
#
|
|
71
|
+
# When true, null values in indexed columns are not included in the index.
|
|
72
|
+
#
|
|
73
|
+
# @return [Boolean] true if null values are ignored, false otherwise
|
|
74
|
+
#
|
|
75
|
+
# @example Check if nulls are ignored
|
|
76
|
+
# index.ignore_nulls? # => false
|
|
77
|
+
def ignore_nulls?
|
|
78
|
+
@java_index.should_ignore_nulls
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def inspect
|
|
82
|
+
"#<Jackcess::Index name=#{name.inspect} columns=#{columns.inspect} primary_key=#{primary_key?}>"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def to_s
|
|
86
|
+
"#{name}: #{columns.join(', ')}"
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|