amalgalite 0.5.1-x86-mswin32-60 → 0.6.0-x86-mswin32-60

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.
@@ -34,7 +34,7 @@ module Amalgalite
34
34
  # the declared data type of the column in the original sql that created the
35
35
  # column
36
36
  attr_accessor :declared_data_type
37
-
37
+
38
38
  # the collation sequence name of the column
39
39
  attr_accessor :collation_sequence_name
40
40
 
@@ -7,6 +7,10 @@ require 'amalgalite/statement'
7
7
  require 'amalgalite/trace_tap'
8
8
  require 'amalgalite/profile_tap'
9
9
  require 'amalgalite/type_maps/default_map'
10
+ require 'amalgalite/function'
11
+ require 'amalgalite/aggregate'
12
+ require 'amalgalite/busy_timeout'
13
+ require 'amalgalite/progress_handler'
10
14
 
11
15
  module Amalgalite
12
16
  #
@@ -31,6 +35,18 @@ module Amalgalite
31
35
  # Error thrown if a database is opened with an invalid mode
32
36
  class InvalidModeError < ::Amalgalite::Error; end
33
37
 
38
+ # Error thrown if there is a failure in a user defined function
39
+ class FunctionError < ::Amalgalite::Error; end
40
+
41
+ # Error thrown if there is a failure in a user defined aggregate
42
+ class AggregateError < ::Amalgalite::Error; end
43
+
44
+ # Error thrown if there is a failure in defining a busy handler
45
+ class BusyHandlerError < ::Amalgalite::Error; end
46
+
47
+ # Error thrown if there is a failure in defining a progress handler
48
+ class ProgressHandlerError < ::Amalgalite::Error; end
49
+
34
50
  ##
35
51
  # container class for holding transaction behavior constants. These are the
36
52
  # SQLite values passed to a START TRANSACTION SQL statement.
@@ -43,7 +59,7 @@ module Amalgalite
43
59
  # a readlock is obtained immediately so that no other process can write to
44
60
  # the database
45
61
  IMMEDIATE = "IMMEDIATE"
46
-
62
+
47
63
  # a read+write lock is obtained, no other proces can read or write to the
48
64
  # database
49
65
  EXCLUSIVE = "EXCLUSIVE"
@@ -81,6 +97,12 @@ module Amalgalite
81
97
  # By default this is an instances of TypeMaps::DefaultMap
82
98
  attr_reader :type_map
83
99
 
100
+ # A list of the user defined functions
101
+ attr_reader :functions
102
+
103
+ # A list of the user defined aggregates
104
+ attr_reader :aggregates
105
+
84
106
  ##
85
107
  # Create a new Amalgalite database
86
108
  #
@@ -114,6 +136,8 @@ module Amalgalite
114
136
  @profile_tap = nil
115
137
  @trace_tap = nil
116
138
  @type_map = ::Amalgalite::TypeMaps::DefaultMap.new
139
+ @functions = Hash.new
140
+ @aggregates = Hash.new
117
141
 
118
142
  unless VALID_MODES.keys.include?( mode )
119
143
  raise InvalidModeError, "#{mode} is invalid, must be one of #{VALID_MODES.keys.join(', ')}"
@@ -509,6 +533,251 @@ module Amalgalite
509
533
  def rollback
510
534
  execute( "ROLLBACK" ) if in_transaction?
511
535
  end
512
- end
536
+
537
+ ##
538
+ # call-seq:
539
+ # db.function( "name", MyDBFunction.new )
540
+ # db.function( "my_func", callable )
541
+ # db.function( "my_func" ) do |x,y|
542
+ # ....
543
+ # return result
544
+ # end
545
+ #
546
+ # register a callback to be exposed as an SQL function. There are multiple
547
+ # ways to register this function:
548
+ #
549
+ # 1. db.function( "name" ) { |a| ... }
550
+ # * pass +function+ a _name_ and a block.
551
+ # * The SQL function _name_ taking _arity_ parameters will be registered,
552
+ # where _arity_ is the _arity_ of the block.
553
+ # * The return value of the block is the return value of the registred
554
+ # SQL function
555
+ # 2. db.function( "name", callable )
556
+ # * pass +function+ a _name_ and something that <tt>responds_to?( :to_proc )</tt>
557
+ # * The SQL function _name_ is registered taking _arity_ parameters is
558
+ # registered where _arity_ is the _arity_ of +callable.to_proc.call+
559
+ # * The return value of the +callable.to_proc.call+ is the return value
560
+ # of the SQL function
561
+ #
562
+ # See also ::Amalgalite::Function
563
+ #
564
+ def define_function( name, callable = nil, &block )
565
+ p = ( callable || block ).to_proc
566
+ raise FunctionError, "Use only mandatory or arbitrary parameters in an SQL Function, not both" if p.arity < -1
567
+ db_function = ::Amalgalite::SQLite3::Database::Function.new( name, p )
568
+ @api.define_function( db_function.name, db_function )
569
+ @functions[db_function.signature] = db_function
570
+ nil
571
+ end
572
+ alias :function :define_function
573
+
574
+ ##
575
+ # call-seq:
576
+ # db.remove_function( 'name', MyScalerFunctor.new )
577
+ # db.remove_function( 'name', callable )
578
+ # db.remove_function( 'name', arity )
579
+ # db.remove_function( 'name' )
580
+ #
581
+ # Remove a function from use in the database. Since the same function may
582
+ # be registered more than once with different arity, you may specify the
583
+ # arity, or the function object, or nil. If nil is used for the arity, then
584
+ # Amalgalite does its best to remove all functions of given name.
585
+ #
586
+ def remove_function( name, callable_or_arity = nil )
587
+ arity = nil
588
+ if callable_or_arity.respond_to?( :to_proc ) then
589
+ arity = callable_or_arity.to_proc.arity
590
+ elsif callable_or_arity.respond_to?( :to_int ) then
591
+ arity = callable_or_arity.to_int
592
+ end
593
+ to_remove = []
594
+
595
+ if arity then
596
+ signature = ::Amalgalite::SQLite3::Database::Function.signature( name, arity )
597
+ db_function = @functions[ signature ]
598
+ raise FunctionError, "db function '#{name}' with arity #{arity} does not appear to be defined" unless db_function
599
+ to_remove << db_function
600
+ else
601
+ possibles = @functions.values.select { |f| f.name == name }
602
+ raise FunctionError, "no db function '#{name}' appears to be defined" if possibles.empty?
603
+ to_remove = possibles
604
+ end
605
+
606
+ to_remove.each do |db_function|
607
+ @api.remove_function( db_function.name, db_function)
608
+ @functions.delete( db_function.signature )
609
+ end
610
+ end
611
+
612
+ ##
613
+ # call-seq:
614
+ # db.define_aggregate( 'name', MyAggregateClass )
615
+ #
616
+ # Define an SQL aggregate function, these are functions like max(), min(),
617
+ # avg(), etc. SQL functions that would be used when a GROUP BY clause is in
618
+ # effect. See also ::Amalgalite::Aggregate.
619
+ #
620
+ # A new instance of MyAggregateClass is created for each instance that the
621
+ # SQL aggregate is mentioned in SQL.
622
+ #
623
+ def define_aggregate( name, klass )
624
+ db_aggregate = klass
625
+ a = klass.new
626
+ raise AggregateError, "Use only mandatory or arbitrary parameters in an SQL Aggregate, not both" if a.arity < -1
627
+ raise AggregateError, "Aggregate implementation name '#{a.name}' does not match defined name '#{name}'"if a.name != name
628
+ @api.define_aggregate( name, a.arity, klass )
629
+ @aggregates[a.signature] = db_aggregate
630
+ nil
631
+ end
632
+ alias :aggregate :define_aggregate
633
+
634
+ ##
635
+ # call-seq:
636
+ # db.remove_aggregate( 'name', MyAggregateClass )
637
+ # db.remove_aggregate( 'name' )
638
+ #
639
+ # Remove an aggregate from use in the database. Since the same aggregate
640
+ # may be refistered more than once with different arity, you may specify the
641
+ # arity, or the aggregate class, or nil. If nil is used for the arity then
642
+ # Amalgalite does its best to remove all aggregates of the given name
643
+ #
644
+ def remove_aggregate( name, klass_or_arity = nil )
645
+ klass = nil
646
+ case klass_or_arity
647
+ when Integer
648
+ arity = klass_or_arity
649
+ when NilClass
650
+ arity = nil
651
+ else
652
+ klass = klass_or_arity
653
+ arity = klass.new.arity
654
+ end
655
+ to_remove = []
656
+ if arity then
657
+ signature = ::Amalgalite::SQLite3::Database::Function.signature( name, arity )
658
+ db_aggregate = @aggregates[ signature ]
659
+ raise AggregateError, "db aggregate '#{name}' with arity #{arity} does not appear to be defined" unless db_aggregate
660
+ to_remove << db_aggregate
661
+ else
662
+ possibles = @aggregates.values.select { |a| a.new.name == name }
663
+ raise AggregateError, "no db aggregate '#{name}' appears to be defined" if possibles.empty?
664
+ to_remove = possibles
665
+ end
666
+
667
+ to_remove.each do |db_aggregate|
668
+ i = db_aggregate.new
669
+ @api.remove_aggregate( i.name, i.arity, db_aggregate )
670
+ @aggregates.delete( i.signature )
671
+ end
672
+ end
673
+
674
+ ##
675
+ # call-seq:
676
+ # db.busy_handler( callable )
677
+ # db.define_busy_handler do |count|
678
+ # end
679
+ # db.busy_handler( Amalgalite::BusyTimeout.new( 30 ) )
680
+ #
681
+ # Register a busy handler for this database connection, the handler MUST
682
+ # follow the +to_proc+ protocol indicating that is will
683
+ # +respond_to?(:call)+. This is intrinsic to lambdas and blocks so
684
+ # those will work automatically.
685
+ #
686
+ # This exposes the sqlite busy handler api to ruby.
687
+ #
688
+ # * http://sqlite.org/c3ref/busy_handler.html
689
+ #
690
+ # The busy handler's _call(N)_ method may be invoked whenever an attempt is
691
+ # made to open a database table that another thread or process has locked.
692
+ # +N+ will be the number of times the _call(N)_ method has been invoked
693
+ # during this locking event.
694
+ #
695
+ # The handler may or maynot be called based upon what SQLite determins.
696
+ #
697
+ # If the handler returns _nil_ or _false_ then no more busy handler calls will
698
+ # be made in this lock event and you are probably going to see an
699
+ # SQLite::Error in your immediately future in another process or in another
700
+ # piece of code.
701
+ #
702
+ # If the handler returns non-nil or non-false then another attempt will be
703
+ # made to obtain the lock, lather, rinse, repeat.
704
+ #
705
+ # If an Exception happens in a busy handler, it will be the same as if the
706
+ # busy handler had returned _nil_ or _false_. The exception itself will not
707
+ # be propogated further.
708
+ #
709
+ def define_busy_handler( callable = nil, &block )
710
+ handler = ( callable || block ).to_proc
711
+ a = handler.arity
712
+ raise BusyHandlerError, "A busy handler expects 1 and only 1 argument, not #{a}" if a != 1
713
+ @api.busy_handler( handler )
714
+ end
715
+ alias :busy_handler :define_busy_handler
716
+
717
+ ##
718
+ # call-seq:
719
+ # db.remove_busy_handler
720
+ #
721
+ # Remove the busy handler for this database connection.
722
+ def remove_busy_handler
723
+ @api.busy_handler( nil )
724
+ end
725
+
726
+ ##
727
+ # call-seq:
728
+ # db.interrupt!
729
+ #
730
+ # Cause another thread with a handle on this database to be interrupted and
731
+ # return at the earliest opportunity as interrupted. It is not safe to call
732
+ # this method if the database might be closed before interrupt! returns.
733
+ #
734
+ def interrupt!
735
+ @api.interrupt!
736
+ end
737
+
738
+ ##
739
+ # call-seq:
740
+ # db.progress_handler( 50, MyProgressHandler.new )
741
+ # db.progress_handler( 25 , callable )
742
+ # db.progress_handler do
743
+ # ....
744
+ # return result
745
+ # end
746
+ #
747
+ # Register a progress handler for this database connection, the handler MUST
748
+ # follow the +to_proc+ protocol indicating that is will
749
+ # +respond_to?(:call)+. This is intrinsic to lambdas and blocks so
750
+ # those will work automatically.
751
+ #
752
+ # This exposes the sqlite progress handler api to ruby.
753
+ #
754
+ # * http://sqlite.org/c3ref/progress_handler.html
755
+ #
756
+ # The progress handler's _call()_ method may be invoked ever N SQLite op
757
+ # codes. If the progress handler returns anything that can evaluate to
758
+ # +true+ then current running sqlite statement is terminated at the earliest
759
+ # oppportunity.
760
+ #
761
+ # You can use this to be notified that a thread is still processingn a
762
+ # request.
763
+ #
764
+ def define_progress_handler( op_code_count = 25, callable = nil, &block )
765
+ handler = ( callable || block ).to_proc
766
+ a = handler.arity
767
+ raise ProgressHandlerError, "A progress handler expects 0 arguments, not #{a}" if a != 0
768
+ @api.progress_handler( op_code_count, handler )
769
+ end
770
+ alias :progress_handler :define_progress_handler
771
+
772
+
773
+ ##
774
+ # call-seq:
775
+ # db.remove_progress_handler
776
+ #
777
+ # Remove the progress handler for this database connection.
778
+ def remove_progress_handler
779
+ @api.progress_handler( nil, nil )
780
+ end
781
+ end
513
782
  end
514
783
 
@@ -0,0 +1,61 @@
1
+ require 'amalgalite/sqlite3/database/function'
2
+ module Amalgalite
3
+ #
4
+ # A Base class to inherit from for creating your own SQL scalar functions
5
+ # in ruby.
6
+ #
7
+ # These are SQL functions similar to _abs(X)_, _length(X)_, _random()_. Items
8
+ # that take parameters and return value. They have no state between
9
+ # calls. Built in SQLite scalar functions are :
10
+ #
11
+ # * http://www.sqlite.org/lang_corefunc.html
12
+ # * http://www.sqlite.org/lang_datefunc.html
13
+ #
14
+ # Functions defined in Amalgalite databases conform to the Proc interface.
15
+ # Everything that is defined in an Amalgalite database using +define_function+
16
+ # has its +to_proc+ method called. As a result, any Function must also
17
+ # conform to the +to_proc+ protocol.
18
+ #
19
+ # If you choose to use Function as a parent class of your SQL scalar function
20
+ # implementation you should only have implement +call+ with the appropriate
21
+ # _arity_.
22
+ #
23
+ # For instance to implement a _sha1(X)_ SQL function you could implement it as
24
+ #
25
+ # class SQLSha1 < ::Amalgalite::Function
26
+ # def initialize
27
+ # super( 'md5', 1 )
28
+ # end
29
+ # def call( s )
30
+ # ::Digest::MD5.hexdigest( s.to_s )
31
+ # end
32
+ # end
33
+ #
34
+ class Function
35
+ # The name of the SQL function
36
+ attr_accessor :name
37
+
38
+ # The arity of the SQL function
39
+ attr_accessor :arity
40
+
41
+ # Initialize the function with a name and arity
42
+ def initialize( name, arity )
43
+ @name = name
44
+ @arity = arity
45
+ end
46
+
47
+ # All SQL functions defined foloow the +to_proc+ protocol
48
+ def to_proc
49
+ self
50
+ end
51
+
52
+ # <b>Do Not Override</b>
53
+ #
54
+ # The function signature for use by the Amaglaite datase in tracking
55
+ # function definition and removal.
56
+ #
57
+ def signature
58
+ @signature ||= ::Amalgalite::SQLite3::Database::Function.signature( self.name, self.arity )
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,21 @@
1
+ module Amalgalite
2
+ ##
3
+ # A base class for use in creating your own progress handler classes
4
+ #
5
+ class ProgressHandler
6
+ def to_proc
7
+ self
8
+ end
9
+
10
+ # the arity of the call method
11
+ def arity() 0 ; end
12
+
13
+ ##
14
+ # Override this method, returning +false+ if the SQLite should act as if
15
+ # +interrupt!+ had been invoked.
16
+ #
17
+ def call
18
+ raise NotImplementedError, "The progress handler call() method must be implemented"
19
+ end
20
+ end
21
+ end
@@ -4,3 +4,4 @@ require 'amalgalite/sqlite3/version'
4
4
  require 'amalgalite/sqlite3/constants'
5
5
  require 'amalgalite/sqlite3/status'
6
6
  require 'amalgalite/sqlite3/database/status'
7
+ require 'amalgalite/sqlite3/database/function'
@@ -0,0 +1,48 @@
1
+ module Amalgalite::SQLite3
2
+ class Database
3
+ ##
4
+ # A wrapper around a proc for use as an SQLite Ddatabase fuction
5
+ #
6
+ # f = Function.new( 'md5', lambda { |x| Digest::MD5.hexdigest( x.to_s ) } )
7
+ #
8
+ class Function
9
+
10
+ # the name of the function, and how it will be called in SQL
11
+ attr_reader :name
12
+
13
+ # The unique signature of this function. This is used to determin if the
14
+ # function is already registered or not
15
+ #
16
+ def self.signature( name, arity )
17
+ "#{name}/#{arity}"
18
+ end
19
+
20
+ # Initialize with the name and the Proc
21
+ #
22
+ def initialize( name, _proc )
23
+ @name = name
24
+ @function = _proc
25
+ end
26
+
27
+ # The unique signature of this function
28
+ #
29
+ def signature
30
+ @signature ||= Function.signature( name, arity )
31
+ end
32
+ alias :to_s :signature
33
+
34
+ # The arity of SQL function, -1 means it is takes a variable number of
35
+ # arguments.
36
+ #
37
+ def arity
38
+ @function.arity
39
+ end
40
+
41
+ # Invoke the proc
42
+ #
43
+ def call( *args )
44
+ @function.call( *args )
45
+ end
46
+ end
47
+ end
48
+ end