cscotta (owner)

Revisions

gist: 160408 Download_button fork
public
Public Clone URL: git://gist.github.com/160408.git
Embed All Files: show embed
tokyo_tdb_adapter.rb #
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
require 'tokyocabinet'
require 'dm-core'
include TokyoCabinet
 
# Hacky overriding for destroy because the original method was constantly
# reporting a new record and failing to pass a valid query to the delete method.
module DataMapper
  module Resource
    def destroy
      return false unless repository.delete({:model => model, :id => id})
      @new_record = true
      repository.identity_map(model).delete(key)
      original_values.clear
      properties.each do |property|
        # We'll set the original value to nil as if we had a new record
        original_values[property.name] = nil if attribute_loaded?(property.name)
      end
      true
    end
 
    # Hacky overriding for save because the original method was constantly reporting a new record.
    def save(context = :default)
      associations_saved = false
      child_associations.each { |a| associations_saved |= a.save }
      saved = update
      original_values.clear if saved
      parent_associations.each { |a| associations_saved |= a.save }
      (saved | associations_saved) == true
    end
 
  end
end
 
 
module DataMapper
  module Adapters
    class TokyoCabinetAdapter < AbstractAdapter
 
      # Opens a database connection and deletes an object identified by the primary key supplied.
      def delete(query)
        item_id = tokyo(query[:model]) { |connection| connection.out(query[:id]) }
        true
      end
 
      # Method for updating the Tokyo TDB datastore when Model.save is called (for create and update).
      def update(attributes, query)
        id = attributes.keys.detect { |k| k.name == :id }
 
        # Creates a new object if the content being saved does not yet contain an ID property.
        unless id
          item_id = tokyo(attributes.keys.first.model) do |connection|
            primary_key = connection.genuid
            tokyo_object = {}
            attributes.each_pair { |k, v| tokyo_object.merge!(k.name.to_s => v.to_s) }
            connection.put(primary_key, tokyo_object)
          end
 
        # Updates an existing object in the datastore by opening a connection, stringifying the
        # key/value pairs, and updating it in the database by saving it using the same primary key.
        else
          item_id = tokyo(attributes.keys.first.model) do |connection|
            tokyo_object = {}
            attributes.each_pair { |k, v| tokyo_object.merge!(k.name.to_s => v.to_s) }
            tokyo_object.delete(id)
            connection.put(attributes[id], tokyo_object)
          end
        end
      end
      
      # Fetches a single object from the datastore.
      def read_one(query)
        read(query, query.model, false)
      end
 
      # Fetches many objects from the datastore.
      def read_many(query)
        read(query, query.model, true)
      end
 
 
      private
 
      # The meat of this adapter.
      def read(query, set, many = true)
        model = query.model
        conditions = query.conditions
        results = []
 
        # If we're just fetching an object by ID, retrieve that single object and return it straightaway.
        if conditions.size == 1 and conditions.first[0] == :eql and conditions.first[1].name == :id
          op, prop, val = conditions.first
          results << fetch_by_id(query, val)
        else
          
          # Open a database connection and initiate a new query
          tokyo(model) do |connection|
            tokyo_query = TDBQRY::new(connection)
            conditions.all? do |tuple|
 
              # Initialize the parameters for the query (including a shim for Ruby 1.9.x compatibility)
              operator, property, bind_value = *tuple
              bind_value = bind_value.flatten.join('') if RUBY_VERSION > '1.9.0' and bind_value.class == Array
 
              # Translate Datamapper's conditions into Tokyo TDBQRY conditions and attach them to the query object.
              unless bind_value.nil? or bind_value.empty?
                case operator
                  when :eql, :in then tokyo_query.addcond(property.name.to_s, TDBQRY::QCSTREQ, bind_value.to_s)
                  when :not then tokyo_query.addcond(property.name.to_s, TDBQRY::QCSTREQ|TDBQRY::QCNEGATE, bind_value.to_s)
                  when :like then tokyo_query.addcond(property.name.to_s, TDBQRY::QCSTRINC, bind_value.to_s)
                  when :gt then tokyo_query.addcond(property.name.to_s, TDBQRY::QCNUMGT, bind_value.to_s)
                  when :gte then tokyo_query.addcond(property.name.to_s, TDBQRY::QCNUMGE, bind_value.to_s)
                  when :lt then tokyo_query.addcond(property.name.to_s, TDBQRY::QCNUMLT, bind_value.to_s)
                  when :lte then tokyo_query.addcond(property.name.to_s, TDBQRY::QCNUMLE, bind_value.to_s)
                  else raise "Invalid query operator: #{operator.inspect} (parameterized SQL not yet supported)."
                end
              end
            end
 
            # Perform the query and retrieve the results.
            dataset = tokyo_query.search
 
            # Build an array of objects with the properties we found in the datastore and return them.
            if dataset.first
              dataset.each do |data|
                properties = connection.get(data)
                properties.merge!({:id => data})
                results << model.new(properties)
              end
            end
          end
        end
        many ? results : results.first
      end
 
      # Fetch a single object by ID by opening a connection, retrieving an object identified
      # by the primary key supplied, and return an object populated with the properties attached.
      def fetch_by_id(query, id, result = nil)
        tokyo(query.model) do |connection|
          dataset = connection.get(id)
          result = query.model.new(dataset.merge!({:id => id})) unless dataset.nil?
        end
        result
      end
 
      # The database conncetion method.
      def tokyo(model, property = nil, &block)
        connection = TDB::new
        attribute = property.to_s.capitalize if property
 
        connection.open(data_path + "#{model}#{attribute}.tct", TDB::OWRITER | TDB::OCREAT)
        result = yield(connection)
        connection.close
 
        result
      end
      
      # Sugar for the connection method.
      # Defines the path to be used for storage based on the Datamapper.setup method used to initialize the ORM.
      def data_path
        data_path = DataMapper.repository.adapter.uri[:data_path].to_s + "/"
      end
 
 
    end # TokyoCabinetAdapter
  end # Adapters
end # Datamapper