# Copyright 2020 Couchbase, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. module Couchbase class LookupInSpec # Fetches the content from a field (if present) at the given path # # @param [String, Symbol] path the path identifying where to get the value. When path is a symbol, the library will try to expand it as macro. # @return [LookupInSpec] def self.get(path) case path when Symbol new(:get, expand_macro(path)) when "" new(:get_doc, "") else new(:get, path) end end # Checks if a value at the given path exists in the document # # @param [String] path the path to check if the field exists # @return [LookupInSpec] def self.exists(path) new(:exists, path) end # Counts the number of values at a given path in teh document # # @param [String] path the path identifying where to count the values # @return [LookupInSpec] def self.count(path) new(:count, path) end def xattr @xattr = true self end def xattr? @xattr end attr_reader :type attr_reader :path private # @api private # # @param [Symbol] macro # @raise [Error::XattrUnknownMacro] if the macro cannot be expanded def self.expand_macro(macro) case macro when :document "$document" when :expiry_time, :expiration_time "$document.exptime" when :cas "$document.CAS" when :seq_no, :sequence_number "$document.seqno" when :last_modified "$document.last_modified" when :is_deleted "$document.deleted" when :value_size_bytes "$document.value_bytes" when :rev_id, :revision_id "$document.revid" else raise Error::XattrUnknownMacro, "unknown macro #{macro}" end end # @param [:get_doc, :get, :exists, :count] type of the lookup # @param [String] path def initialize(type, path) @xattr = false @type = type @path = path end end class MutateInSpec # Creates a command with the intention of replacing an existing value in a JSON document. # # If the path is empty (""), then the value will be used for the document's full body. Will # error if the last element of the path does not exist. # # @param [String] path the path identifying where to replace the value. # @param [Object, Symbol] value the value to replace with. # When symbol specified and it is matches to known macro, it will be expanded # # @return [MutateInSpec] def self.replace(path, value) new(path.empty? ? :set_doc : :replace, path, value) end # Creates a command with the intention of inserting a new value in a JSON object. # # Will error if the last element of the path already exists. # # @param [String] path the path identifying where to insert the value. # @param [Object, Symbol] value the value to insert. # When symbol specified and it is matches to known macro, it will be expanded # # @return [MutateInSpec] def self.insert(path, value) new(:dict_add, path, value) end # Creates a command with the intention of removing an existing value in a JSON object. # # Will error if the path does not exist. # # @param path the path identifying what to remove. # # @return [MutateInSpec] def self.remove(path) new(:remove, path, nil) end # Creates a command with the intention of upserting a value in a JSON object. # # That is, the value will be replaced if the path already exists, or inserted if not. # # @param [String] path the path identifying where to upsert the value. # @param [Object, Symbol] value the value to upsert. # When symbol specified and it is matches to known macro, it will be expanded # # @return [MutateInSpec] def self.upsert(path, value) new(:dict_upsert, path, value) end # Creates a command with the intention of appending a value to an existing JSON array. # # Will error if the last element of the path does not exist or is not an array. # # @param [String] path the path identifying an array to which to append the value. # @param [Array] values the value(s) to append. # # @return [MutateInSpec] def self.array_append(path, values) new(:array_push_last, path, values) end # Creates a command with the intention of prepending a value to an existing JSON array. # # Will error if the last element of the path does not exist or is not an array. # # @param [String] path the path identifying an array to which to append the value. # @param [Array] values the value(s) to prepend. # # @return [MutateInSpec] def self.array_prepend(path, values) new(:array_push_first, path, values) end # Creates a command with the intention of inserting a value into an existing JSON array. # # Will error if the last element of the path does not exist or is not an array. # # @param [String] path the path identifying an array to which to append the value, and an index. E.g. "foo.bar[3]" # @param [Array] values the value(s) to insert. # # @return [MutateInSpec] def self.array_insert(path, values) new(:array_insert, path, values) end # Creates a command with the intent of inserting a value into an existing JSON array, but only if the value # is not already contained in the array (by way of string comparison). # # Will error if the last element of the path does not exist or is not an array. # # @param [String] path the path identifying an array to which to append the value, and an index. E.g. "foo.bar[3]" # @param [Object, Symbol] value the value to insert. # # @return [MutateInSpec] def self.array_add_unique(path, value) new(:array_add_unique, path, value) end # Creates a command with the intent of incrementing a numerical field in a JSON object. # # If the field does not exist, then it is created and takes the value of +delta+ # # @param [String] path the path identifying a numerical field to adjust or create # @param [Integer] delta the value to increment the field by # # @return [MutateInSpec] def self.increment(path, delta) new(:counter, path, delta.abs) end # Creates a command with the intent of decrementing a numerical field in a JSON object. # # If the field does not exist, then it is created and takes the value of +delta+ * -1 # # @param [String] path the path identifying a numerical field to adjust or create # @param [Integer] delta the value to decrement the field by # # @return [MutateInSpec] def self.decrement(path, delta) new(:counter, path, -1 * delta.abs) end def xattr @xattr = true self end def create_path @create_path = true self end def xattr? @xattr end def create_path? @create_path end def expand_macros? @expand_macros end CAS = "${Mutation.CAS}" SEQ_NO = "${Mutation.seqno}" VALUE_CRC32C = "${Mutation.value_crc32c}" attr_reader :type attr_reader :path attr_reader :param private def initialize(type, path, param) @create_path = false @param = nil @xattr = false @type = type @path = path @param = case param when :cas CAS when :seq_no, :sequence_number SEQ_NO when :value_crc32c, :value_crc VALUE_CRC32C else param end @expand_macros = [CAS, SEQ_NO, VALUE_CRC32C].include?(@param) @xattr = true if @expand_macros unless @param.nil? @param = case type when :counter @param.to_i when :array_push_first, :array_push_last, :array_insert @param.map { |entry| JSON.generate(entry) }.join(",") else JSON.generate(@param) end end end end end