From 6d723a0286bc46de38dd151b228c6735db23628f Mon Sep 17 00:00:00 2001 From: Cezar Andrei Date: Fri, 13 Feb 2026 12:11:19 -0600 Subject: [PATCH 1/6] Added support for last write metadata and creation time: new API, implementation and test. The problem remaining is that the server can enable a feature or not, also the proxy can enable it or not. --- CHANGELOG.md | 7 + README.md | 2 +- examples/config.py | 4 +- src/borneo/common.py | 8 + src/borneo/nson.py | 36 ++++- src/borneo/nson_protocol.py | 4 + src/borneo/operations.py | 297 ++++++++++++++++++++++++++++++++++-- src/borneo/serdeutil.py | 2 +- test/config.py | 40 +++-- test/put.py | 66 +++++++- test/test_base.py | 9 +- 11 files changed, 427 insertions(+), 48 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0abfe65..4870864 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/). ==================== +# 5.5.0 - 2026-02-06 + +## Added + +- support for row creation time +- support for last write metadata of a row + # 5.4.3 - 2025-08-15 ## Fixed diff --git a/README.md b/README.md index 5083cba..7e03f33 100644 --- a/README.md +++ b/README.md @@ -405,7 +405,7 @@ operate correctly. A secure configuration requires a secure proxy and more complex configuration. 1. Start the Oracle NoSQL Database and proxy server based on instructions above. - Note the HTTP port used. By default the endpoint is *localhost:80*. + Note the HTTP port used. By default, the endpoint is *localhost:80*. 2. The *quickstart.py* program defaults to *localhost:80*. If the proxy was started using a different host or port edit the settings accordingly. diff --git a/examples/config.py b/examples/config.py index 08c0f28..c126194 100644 --- a/examples/config.py +++ b/examples/config.py @@ -19,7 +19,7 @@ # The endpoint to use to connect to the service. This endpoint is for a Cloud # Simulator running on its default port (8080) on the local machine. -endpoint = 'localhost:8080' +endpoint = 'http://localhost:8080' # The server type. -server_type = 'cloudsim' +server_type = 'onprem' diff --git a/src/borneo/common.py b/src/borneo/common.py index 38dc8e5..e089246 100644 --- a/src/borneo/common.py +++ b/src/borneo/common.py @@ -232,6 +232,14 @@ def check_str(data, name, allow_none=False): raise IllegalArgumentException( name + ' must be a string that is not empty.') + @staticmethod + def check_json_construct(data, name): + if not(data is None or isinstance(data, dict) or isinstance(data, list) + or isinstance(data, str) or isinstance(data, int) or + isinstance(data, float) or isinstance(data, Decimal) or + isinstance(data, bool)): + raise IllegalArgumentException(name + ' must be a jason construct.') + @staticmethod def is_digit(data): if (isinstance(data, int) or diff --git a/src/borneo/nson.py b/src/borneo/nson.py index a620906..660501a 100644 --- a/src/borneo/nson.py +++ b/src/borneo/nson.py @@ -842,11 +842,13 @@ def deserialize(self, request, bis, serial_version): # "READ_KV" : # "WRITE_UNITS" : # } -# "ROW" : { # the row plus metadata -# "MODIFIED" : # last modified -# "EXPIRATION" : # expiration if using TTL -# "ROW_VERSION" : # kv version -# "VALUE" : { # the row's value in NSON +# "ROW" : { # the row plus metadata +# "CREATION_TIME" : # creation time +# "LAST_WRITE_METADATA" : # last write metadata +# "MODIFIED" : # last modified +# "EXPIRATION" : # expiration if using TTL +# "ROW_VERSION" : # kv version +# "VALUE" : { # the row's value in NSON # } # } # } @@ -989,6 +991,9 @@ def write_put_request(ns, request): if request.get_match_version() is not None: Proto.write_bin_map_field( ns, ROW_VERSION, request.get_match_version().get_bytes()) + if request.get_last_write_metadata() is not None: + Proto.write_string_map_field(ns, LAST_WRITE_METADATA, + json.dumps(request.get_last_write_metadata())) Proto.write_value(ns, request.get_value()) @@ -1067,6 +1072,9 @@ def write_delete_request(ns, request): if request.get_match_version() is not None: Proto.write_bin_map_field( ns, ROW_VERSION, request.get_match_version().get_bytes()) + if request.get_last_write_metadata() is not None: + Proto.write_string_map_field(ns, LAST_WRITE_METADATA, + json.dumps(request.get_last_write_metadata())) Proto.write_key(ns, request.get_key()) @@ -1530,6 +1538,9 @@ def serialize(self, request, bos, serial_version): Proto.write_bin_map_field(ns, CONTINUATION_KEY, request.get_continuation_key()) Proto.write_durability(ns, request) + if request.get_last_write_metadata() is not None: + Proto.write_string_map_field(ns, LAST_WRITE_METADATA, + json.dumps(request.get_last_write_metadata())) self._write_field_range(ns, request.get_range()) Proto.write_key(ns, request.get_key()) @@ -1892,6 +1903,9 @@ def serialize(self, request, bos, serial_version): request.get_query_name()) if request.get_virtual_scan() is not None: Proto.write_virtual_scan(ns, request.get_virtual_scan()) + if request.get_last_write_metadata() is not None: + Proto.write_string_map_field(ns, LAST_WRITE_METADATA, + json.dumps(request.get_last_write_metadata())) Proto.end_map(ns, PAYLOAD) @@ -2497,7 +2511,9 @@ def read_row(bis, result): while walker.has_next(): walker.next() name = walker.get_current_name() - if name == MODIFIED: + if name == CREATION_TIME: + result.set_creation_time(Nson.read_long(bis)) + elif name == MODIFIED: result.set_modification_time(Nson.read_long(bis)) elif name == EXPIRATION: result.set_expiration_time(Nson.read_long(bis)) @@ -2506,6 +2522,8 @@ def read_row(bis, result): Version.create_version(Nson.read_binary(bis))) elif name == VALUE: result.set_value(Proto.nson_to_value(bis)) + elif name == LAST_WRITE_METADATA: + result.set_last_write_metadata(json.loads(Nson.read_string(bis))) else: walker.skip() @@ -2588,13 +2606,17 @@ def read_return_info(bis, result): while walker.has_next(): walker.next() name = walker.get_current_name() - if name == EXISTING_MOD_TIME: + if name == CREATION_TIME: + result.set_existing_creation_time(Nson.read_long(bis)) + elif name == EXISTING_MOD_TIME: result.set_existing_modification_time(Nson.read_long(bis)) elif name == EXISTING_VERSION: result.set_existing_version(Version.create_version( Nson.read_binary(bis))) elif name == EXISTING_VALUE: result.set_existing_value(Proto.nson_to_value(bis)) + elif name == EXISTING_LAST_WRITE_METADATA: + result.set_existing_last_write_metadata(json.loads(Nson.read_string(bis))) else: walker.skip() diff --git a/src/borneo/nson_protocol.py b/src/borneo/nson_protocol.py index 00cd82c..30a5cfd 100644 --- a/src/borneo/nson_protocol.py +++ b/src/borneo/nson_protocol.py @@ -142,13 +142,16 @@ # # row metadata # +CREATION_TIME = 'ct' EXPIRATION = 'xp' +LAST_WRITE_METADATA = 'mt' MODIFIED = 'md' ROW = 'r' ROW_VERSION = 'rv' # # operation metadata # +EXISTING_LAST_WRITE_METADATA = 'ed' EXISTING_MOD_TIME = 'em' EXISTING_VALUE = 'el' EXISTING_VERSION = 'ev' @@ -164,6 +167,7 @@ NOT_TARGET_TABLES = 'nt' NUM_RESULTS = 'nr' PROXY_TOPO_SEQNUM = 'pn' +QUERY_NAME = 'qn' QUERY_OPERATION = 'qo' QUERY_PLAN_STRING = 'qs' QUERY_RESULTS = 'qr' diff --git a/src/borneo/operations.py b/src/borneo/operations.py index 576a26d..8a2eb60 100644 --- a/src/borneo/operations.py +++ b/src/borneo/operations.py @@ -447,7 +447,7 @@ class WriteRequest(Request): This class encapsulates the common parameters of table name and the return row boolean, which allows applications to get information about the existing - value of the target row on failure. By default no previous information is + value of the target row on failure. By default, no previous information is returned. """ @@ -455,6 +455,7 @@ def __init__(self): super(WriteRequest, self).__init__() self._return_row = False self._durability = None + self._last_write_metadata = None def __str__(self): return 'WriteRequest' @@ -499,6 +500,49 @@ def get_type_name(): # type: () -> str return "Write" + def get_last_write_metadata(self): + """ + Returns the last write metadata to be used for this request or None if + not set. + + :rtype: int | float | str | bool | dict | list | None + :versionadded:: 5.5.0 + """ + return self._last_write_metadata + + def set_last_write_metadata(self, last_write_metadata): + """ + Sets the write metadata to use for this request. + + This is an optional parameter. + + Last write metadata is associated to a certain version of a row. Any + subsequent write operation will use its own write metadata value. If not + specified null will be used by default. + NOTE that if you have previously written a record with metadata and a + subsequent write does not supply metadata, the metadata associated with + the row will be null. Therefore, if you wish to have metadata + associated with every write operation, you must supply a valid JSON + construct to this method. + + :param last_write_metadata: the write metadata, must be None or a valid + JSON construct: object, array, string, number, true, false or None, + otherwise an IllegalArgumentException is thrown. + :type last_write_metadata: int | float | str | bool | dict | list | None + :return: self + :raises IllegalArgumentException: if last_write_metadata not None nor + JSON construct + :versionadded:: 5.5.0 + """ + if last_write_metadata is None: + self._last_write_metadata = None + return self + + + CheckValue.check_json_construct(last_write_metadata, "last_write_metadata") + self._last_write_metadata = last_write_metadata + return self + class ReadRequest(Request): """ @@ -506,7 +550,7 @@ class ReadRequest(Request): :py:meth:`NoSQLHandle.get`. This class encapsulates the common parameters of table name and consistency. - By default read operations use Consistency.EVENTUAL. Use of + By default, read operations use Consistency.EVENTUAL. Use of Consistency.ABSOLUTE should be used only when required as it incurs additional cost. """ @@ -1469,6 +1513,7 @@ def __init__(self): self._range = None self._max_write_kb = 0 self._durability = None + self._last_write_metadata = None def __str__(self): return 'MultiDeleteRequest' @@ -1724,6 +1769,49 @@ def get_request_name(self): # type: () -> str return "MultiDelete" + def get_last_write_metadata(self): + """ + Returns the last write metadata to be used for this request or None if + not set. + + :rtype: int | float | str | bool | dict | list | None + :versionadded:: 5.5.0 + """ + return self._last_write_metadata + + def set_last_write_metadata(self, last_write_metadata): + """ + Sets the write metadata to use for this request. + + This is an optional parameter. + + Last write metadata is associated to a certain version of a row. Any + subsequent write operation will use its own write metadata value. If not + specified null will be used by default. + NOTE that if you have previously written a record with metadata and a + subsequent write does not supply metadata, the metadata associated with + the row will be null. Therefore, if you wish to have metadata + associated with every write operation, you must supply a valid JSON + construct to this method. + + :param last_write_metadata: the write metadata, must be None or a valid + JSON construct: object, array, string, number, true, false or None, + otherwise an IllegalArgumentException is thrown. + :type last_write_metadata: int | float | str | bool | dict | list | None + :return: self + :raises IllegalArgumentException: if last_write_metadata not None nor + JSON construct + :versionadded:: 5.5.0 + """ + if last_write_metadata is None: + self._last_write_metadata = None + return self + + + CheckValue.check_json_construct(last_write_metadata, "last_write_metadata") + self._last_write_metadata = last_write_metadata + return self + class PrepareRequest(Request): """ @@ -2462,6 +2550,7 @@ def __init__(self): self._driver_query_trace = None self._server_query_traces = None # dict if set self._batch_counter = 0 + self._last_write_metadata = None # dict, list, string, number, bool or None def __str__(self): return 'QueryRequest' @@ -3011,6 +3100,45 @@ def get_request_name(self): # type: () -> str return "Query" + def get_last_write_metadata(self): + """ + Returns the last write metadata to be used for this request or None if + not set. + + :rtype: int | float | str | bool | dict | list | None + :versionadded:: 5.5.0 + """ + return self._last_write_metadata + + def set_last_write_metadata(self, last_write_metadata): + """ + Sets the write metadata to use for the operation. This setting is + optional and only applies if the query modifies or deletes any rows + using an INSERT, UPDATE, UPSERT or DELETE statement. If the query is + read-only this setting is ignored. This is an optional parameter. + + Write metadata is associated to a certain version of a row. Any + subsequent write operation will use its own write metadata value. If not + specified null will be used by default. + NOTE that if you have previously written a record with metadata and a + subsequent write does not supply metadata, the metadata associated with + the row will be null. Therefore, if you wish to have metadata + associated with every write operation, you must supply a valid JSON + construct to this method. + + :param last_write_metadata: the write metadata, must be None or a valid + JSON construct: object, array, string, number, true, false or None, + otherwise an IllegalArgumentException is thrown. + :type last_write_metadata: int | float | str | bool | dict | list | None + :return: self + :raises IllegalArgumentException: if last_write_metadata not None nor + JSON construct + :versionadded:: 5.5.0 + """ + if last_write_metadata is None: + self._last_write_metadata = None + return self + class SystemRequest(Request): """ @@ -4729,7 +4857,9 @@ def __init__(self): super(WriteResult, self).__init__() self._existing_version = None self._existing_value = None + self._existing_creation_time = 0 self._existing_modification_time = 0 + self._existing_last_write_metadata = None def set_existing_value(self, existing_value): self._existing_value = existing_value @@ -4739,29 +4869,47 @@ def set_existing_version(self, existing_version): self._existing_version = existing_version return self + def set_existing_creation_time(self, existing_creation_time): + self._existing_creation_time = existing_creation_time + return self + def set_existing_modification_time(self, existing_modification_time): self._existing_modification_time = existing_modification_time return self + def set_existing_last_write_metadata(self, existing_last_write_metadata): + self._existing_last_write_metadata = existing_last_write_metadata + return self + def _get_existing_value(self): return self._existing_value def _get_existing_version(self): return self._existing_version + def _get_existing_creation_time(self): + return self._existing_creation_time + def _get_existing_modification_time(self): return self._existing_modification_time + def _get_existing_last_write_metadata(self): + return self._existing_last_write_metadata + + def _set_existing_last_write_metadata(self, value): + self._existing_last_write_metadata = value class DeleteResult(WriteResult): """ Represents the result of a :py:meth:`NoSQLHandle.delete` operation. - If the delete succeeded :py:meth:`get_success` returns True. Information - about the existing row on failure may be available using - :py:meth:`get_existing_value`, :py:meth:`get_existing_version` and - :py:meth:`get_existing_modification_time`, depending - on the use of :py:meth:`DeleteRequest.set_return_row`. + If the delete operation succeeded :py:meth:`get_success` returns True. + Information about the existing row on failure may be available using + :py:meth:`get_existing_value`, :py:meth:`get_existing_version`, + :py:meth:`get_existing_creation_time`, + :py:meth:`get_existing_modification_time` and + :py:meth:`get_existing_last_write_metadata`, depending on the use of + :py:meth:`DeleteRequest.set_return_row`. """ def __init__(self): @@ -4806,18 +4954,42 @@ def get_existing_version(self): """ return self._get_existing_version() + def get_existing_creation_time(self): + """ + Returns the existing row creation time if available. See + :py:meth:`DeleteRequest.set_return_row` for conditions under which + the information is available. + + :returns: the creation time in milliseconds since January 1, 1970, UTC + :rtype: int + :versionadded:: 5.5.0 + """ + return self._get_existing_creation_time() + def get_existing_modification_time(self): """ Returns the existing row modification time if available. See :py:meth:`DeleteRequest.set_return_row` for conditions under which the information is available. - :returns: the modification time in milliseconds since January 1, 1970 + :returns: the modification time in milliseconds since January 1, 1970, UTC :rtype: int :versionadded:: 5.3.0 """ return self._get_existing_modification_time() + def get_existing_last_write_metadata(self): + """ + Returns the existing last write metadata of the returned row if + available. See :py:meth:`DeleteRequest.set_return_row` for conditions + under which the information is available. + + :returns: the last write metadata. + :rtype: int | float | str | bool | dict | list | None + :versionadded:: 5.5.0 + """ + return self._get_existing_last_write_metadata() + def get_read_kb(self): """ Returns the read throughput consumed by this operation, in KBytes. This @@ -4875,7 +5047,9 @@ def __init__(self): self._value = None self._version = None self._expiration_time = 0 + self._creation_time = 0 self._modification_time = 0 + self._last_write_metadata = None def __str__(self): return 'None' if self._value is None else str(self._value) @@ -4921,12 +5095,28 @@ def get_expiration_time(self): row does not expire. This value is valid only if the operation successfully returned a row (:py:meth:`get_value` returns non-none). - :returns: the expiration time in milliseconds since January 1, 1970, or - zero if the row never expires or the row does not exist. + :returns: the expiration time in milliseconds since January 1, 1970, UTC, + or zero if the row never expires or the row does not exist. :rtype: int """ return self._expiration_time + def set_creation_time(self, creation_time): + # Sets the creation time, internal + self._creation_time = creation_time + return self + + def get_creation_time(self): + """ + Returns the creation time of the row. This value is valid only if + the operation successfully returned a row (:py:meth:`get_value` returns non-none). + + :returns: the creation time in milliseconds since January 1, 1970, UTC + :rtype: int + :versionadded: 5.5.0 + """ + return self._creation_time + def set_modification_time(self, modification_time): # Sets the modification time, internal self._modification_time = modification_time @@ -4937,8 +5127,9 @@ def get_modification_time(self): Returns the modification time of the row. This value is valid only if the operation successfully returned a row (:py:meth:`get_value` returns non-none). - :returns: the modification time in milliseconds since January 1, 1970 + :returns: the modification time in milliseconds since January 1, 1970, UTC :rtype: int + :versionadded: 5.5.0 """ return self._modification_time @@ -4983,6 +5174,40 @@ def get_write_units(self): """ return super(GetResult, self).get_write_units() + def get_last_write_metadata(self): + """ + Returns the last write metadata to be used for this request or None if + not set. + + :rtype: int | float | str | bool | dict | list | None + :versionadded:: 5.5.0 + """ + return self._last_write_metadata + + def set_last_write_metadata(self, last_write_metadata): + """ + Internal use only. + + Sets the write metadata of this object. + + :param last_write_metadata: the write metadata, must be None or a valid + JSON construct: object, array, string, number, true, false or None, + otherwise an IllegalArgumentException is thrown. + :type last_write_metadata: int | float | str | bool | dict | list | None + :return: self + :raises IllegalArgumentException: if last_write_metadata not None nor + JSON construct + :versionadded:: 5.5.0 + """ + if last_write_metadata is None: + self._last_write_metadata = None + return self + + + CheckValue.check_json_construct(last_write_metadata, "last_write_metadata") + self._last_write_metadata = last_write_metadata + return self + class GetIndexesResult(Result): """ @@ -5300,13 +5525,25 @@ def get_existing_value(self): """ return self._get_existing_value() + def get_existing_creation_time(self): + """ + Returns the existing row creation time if available. See + :py:meth:`PutRequest.set_return_row` for conditions under which + the information is available. + + :returns: the creation time in milliseconds since January 1, 1970 UTC + :rtype: int + :versionadded:: 5.5.0 + """ + return self._get_existing_creation_time() + def get_existing_modification_time(self): """ - Returns the existing row modification timeif available. See + Returns the existing row modification time if available. See :py:meth:`PutRequest.set_return_row` for conditions under which the information is available. - :returns: the modification time in milliseconds since January 1, 1970 + :returns: the modification time in milliseconds since January 1, 1970 UTC :rtype: int :versionadded:: 5.3.0 """ @@ -5353,6 +5590,18 @@ def get_write_units(self): """ return super(PutResult, self).get_write_units() + def get_existing_last_write_metadata(self): + """ + Returns the existing row last write metadata if available. See + :py:meth:`PutRequest.set_return_row` for conditions under which + the information is available. + + :returns: the last write metadata + :rtype: int | float | str | bool | dict | list | None + :versionadded:: 5.5.0 + """ + return self._get_existing_last_write_metadata() + class QueryResult(Result): """ @@ -6576,16 +6825,36 @@ def get_existing_value(self): """ return self._get_existing_value() + def get_existing_creation_time(self): + """ + Returns the existing row creation time if available. + + :returns: the creation time in milliseconds since January 1, 1970, UTC + :rtype: int + :versionadded:: 5.5.0 + """ + return self._get_existing_creation_time() + def get_existing_modification_time(self): """ Returns the existing row modification time if available. - :returns: the modification time in milliseconds since January 1, 1970 + :returns: the modification time in milliseconds since January 1, 1970, UTC :rtype: int :versionadded:: 5.3.0 """ return self._get_existing_modification_time() + def get_existing_last_write_metadata(self): + """ + Returns the existing row last write metadata if available or None + otherwise. + + :returns: the last write metadata + :rtype: int | float | str | bool | dict | list | None + :versionadded:: 5.5.0 + """ + return self._get_existing_last_write_metadata() class ReplicaStatsResult(Result): """ diff --git a/src/borneo/serdeutil.py b/src/borneo/serdeutil.py index 565c681..beb5b53 100644 --- a/src/borneo/serdeutil.py +++ b/src/borneo/serdeutil.py @@ -147,7 +147,7 @@ def stop(self): # noinspection PyTypeChecker class SerdeUtil(object): """ - A class to encapsulte static methods used by serialization and + A class to encapsulate static methods used by serialization and deserialization of requests. These utility methods can be used by multiple protocols. It also includes constants that are shared across protocols. diff --git a/test/config.py b/test/config.py index 8f8838c..b429581 100644 --- a/test/config.py +++ b/test/config.py @@ -3,28 +3,38 @@ # # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl/ +# # -# Parameters used by test code -- Cloud Simulator Configuration +# Parameters used by test code -- On-premise Oracle NoSQL database # -# This file is configured for the unit tests to be run against a Cloud Simulator -# instance. The simulator is used so that limits that exist in the cloud service -# are not involved and there is no cost involved in running the unit tests. +# This file is configured for the unit tests to be run against a On-prem Oracle +# NoSQL database. Please start the database and proxy first. # -# The default settings below are sufficient if the Cloud Simulator has been -# started on the endpoint, localhost:8080, which is its default. If not, the -# parameters in this file should be changed as needed. This is a backup of -# config.py, when config.py is overwritten by other config*.py, the default -# config.py of Cloud Simulator is back up in this file. +# The default settings below are sufficient if the On-prem proxy has been +# started on the endpoint, localhost:8080, with security disable. If not, the +# parameters in this file should be changed as needed. # -# The endpoint to use to connect to the service. This endpoint is for a Cloud -# Simulator running on its default port (8080) on the local machine. -endpoint = 'localhost:8080' +# The endpoint to use to connect to the service. This endpoint is for a on-prem +# proxy started by the customer. Use 'http' protocol for non-secure database and +# 'https' for secure database. +endpoint = 'http://localhost:8080' # The server type, please don't change it. -server_type = 'cloudsim' +server_type = 'onprem' -# Cloud Simulator version. Use None to test with the latest Cloud Simulator -# version, a specified version should be like "1.4.0". +# On-prem Oracle NoSQL database version. Use None to test with the latest NoSQL +# Database version, a specified version should be like "20.2.0". version = None + +# Please set the following parameters if running against secure database. + +# SSL CA certificates. Configure it to specify CA certificates or set +# REQUESTS_CA_BUNDLE environment variable when running against a secure +# database. For non-secure database, use the default None. +ca_certs = None +# User name for secure database, for non-secure database, use the default None. +user_name = None +# Password for secure database, for non-secure database, use the default None. +user_password = None diff --git a/test/put.py b/test/put.py index 8bd0465..2942953 100644 --- a/test/put.py +++ b/test/put.py @@ -14,9 +14,11 @@ from time import time from borneo import ( - DeleteRequest, Durability, GetRequest, IllegalArgumentException, IllegalStateException, + DeleteRequest, Durability, GetRequest, IllegalArgumentException, + IllegalStateException, PutOption, PutRequest, RequestSizeLimitException, TableLimits, - TableNotFoundException, TableRequest, TimeToLive, TimeUnit) + TableNotFoundException, TableRequest, TimeToLive, TimeUnit, PutResult, + GetResult) from parameters import is_onprem, table_name, tenant_id, timeout from test_base import TestBase from testutils import get_row @@ -27,6 +29,10 @@ class TestPut(unittest.TestCase, TestBase): + def __init__(self, methodName='runTest'): + unittest.TestCase.__init__(self, methodName) + TestBase.__init__(self) + @classmethod def setUpClass(cls): cls.set_up_class() @@ -166,10 +172,13 @@ def testPutSetTtlAndUseTableDefaultTtl(self): def testPutGets(self): identity_cache_size = 5 version = self.handle.put(self.put_request).get_version() - self.put_request.set_option(PutOption.IF_ABSENT).set_match_version( - version).set_ttl(self.ttl).set_use_table_default_ttl( - True).set_exact_match(True).set_identity_cache_size( - identity_cache_size).set_return_row(True) + self.put_request.set_option(PutOption.IF_ABSENT)\ + .set_match_version(version)\ + .set_ttl(self.ttl)\ + .set_use_table_default_ttl(True)\ + .set_exact_match(True)\ + .set_identity_cache_size(identity_cache_size)\ + .set_return_row(True) self.assertEqual(self.put_request.get_value(), self.row) self.assertEqual(self.put_request.get_compartment(), tenant_id) self.assertEqual(self.put_request.get_option(), PutOption.IF_ABSENT) @@ -184,6 +193,51 @@ def testPutGets(self): identity_cache_size) self.assertTrue(self.put_request.get_return_row()) + def testPutLastWriteMetadataAndCreationTime(self): + last_write_meta = {'a':1, 'b':[0, "str", None, [], {}, True, False]} + self.put_request.set_return_row(True)\ + .set_last_write_metadata(last_write_meta) + + self.assertEqual(self.put_request.get_last_write_metadata(), last_write_meta) + + result = None + + if not self.feature_last_write_metadata_is_enabled: + try: + result = self.handle.put(self.put_request) + self.fail("handle.put(last_write_metadata) should have failed") + except IllegalArgumentException: + self.assertTrue(True) + else: + result = self.handle.put(self.put_request) + + self.assertIsNotNone(result) + self.assertEqual(result.get_existing_last_write_metadata(), None) + self.assertEqual(result.get_existing_creation_time(), 0) + + result = self.handle.get(self.get_request) + self.assertIsNotNone(result) + self.assertEqual(result.get_last_write_metadata(), last_write_meta) + + last_write_meta2 = {'abc': 123} + self.put_request.set_return_row(True) \ + .set_last_write_metadata(last_write_meta2) + result = self.handle.put(self.put_request) + + self.assertIsNotNone(result) + self.assertEqual(result.get_existing_last_write_metadata(), + last_write_meta) + + result = self.handle.get(self.get_request) + self.assertIsNotNone(result) + self.assertEqual(result.get_last_write_metadata(), last_write_meta2) + + # check creation time too + if not self.feature_creation_time_is_enabled: + self.assertEqual(result.get_creation_time(), 0) + else: + self.assertGreater(result.get_creation_time(), 1) + def testPutIllegalRequest(self): self.assertRaises(IllegalArgumentException, self.handle.put, 'IllegalRequest') diff --git a/test/test_base.py b/test/test_base.py index 641e968..32746d0 100644 --- a/test/test_base.py +++ b/test/test_base.py @@ -29,6 +29,8 @@ class TestBase(object): def __init__(self): self.handle = None + self.feature_creation_time_is_enabled = True + self.feature_last_write_metadata_is_enabled = True def check_cost(self, result, read_kb, read_units, write_kb, write_units, advance=False, multi_shards=False): @@ -58,7 +60,7 @@ def check_cost(self, result, read_kb, read_units, write_kb, write_units, def check_get_result(self, result, value=None, exp_version=None, expect_expiration=0, timeunit=None, ver_eq=True, - mod_time_recent=False): + mod_time_recent=False, serial_version=0): assert isinstance(self, TestCase) # check value self.assertEqual(result.get_value(), value) @@ -70,6 +72,7 @@ def check_get_result(self, result, value=None, exp_version=None, self.assertEqual(ver.get_bytes(), exp_version.get_bytes()) else: self.assertNotEqual(ver.get_bytes(), exp_version.get_bytes()) + # check expiration time if expect_expiration == 0: self.assertEqual(result.get_expiration_time(), 0) @@ -81,6 +84,8 @@ def check_get_result(self, result, value=None, exp_version=None, self.assertLess(actual_expect_diff, TestBase.HOUR_IN_MILLIS) else: self.assertLess(actual_expect_diff, TestBase.DAY_IN_MILLIS) + + # check modification time modtime = result.get_modification_time() if mod_time_recent: now = round(time() * 1000) @@ -224,4 +229,4 @@ def table_request(cls, request, test_handle=None): # if is_pod(): sleep(30) - test_handle.do_table_request(request, wait_timeout, 1000) + test_handle.do_table_request(request, wait_timeout, 1000) \ No newline at end of file From 88d4ca01f5d7ad337329eb90d78465205816b9fc Mon Sep 17 00:00:00 2001 From: Cezar Andrei Date: Fri, 20 Feb 2026 13:58:23 -0600 Subject: [PATCH 2/6] Added features header, with LAST_WRITE_METADATA and CREATION_TIME features. --- README.md | 2 +- src/borneo/client.py | 33 ++++++++++++++++++- src/borneo/http.py | 22 +++++++++++++ test/put.py | 75 ++++++++++++++++++++++++++------------------ 4 files changed, 100 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 7e03f33..f11097d 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ install the oci package:: pip install oci -See [the installation guide](https://nosql-python-sdk.readthedocs.io/en/stable/installation.html) for additional requirements and and alternative install +See [the installation guide](https://nosql-python-sdk.readthedocs.io/en/stable/installation.html) for additional requirements and alternative install methods. ## Examples diff --git a/src/borneo/client.py b/src/borneo/client.py index 57e7664..17f7132 100644 --- a/src/borneo/client.py +++ b/src/borneo/client.py @@ -35,6 +35,10 @@ class Client(object): LIMITER_REFRESH_NANOS = 600000000000 TRACE_LEVEL = 0 + # features flag bits + FEATURE_FLAG_LAST_WRITE_METADATA = 1 << 0; + FEATURE_FLAG_CREATION_TIME = 1 << 1; + # The HTTP driver client. def __init__(self, config, logger): self._logutils = LogUtils(logger) @@ -117,6 +121,7 @@ def __init__(self, config, logger): self._stats_control = StatsControl(config, logger, config.get_rate_limiting_enabled()) + self._features = 0 @synchronized def background_update_limiters(self, table_name): @@ -592,9 +597,35 @@ def get_kv_version(self): return self._kv_version def set_proxy_info(self, proxy_header): + """ + Format of the server version header string: + proxy=X.Y.Z kv=X.Y.Z[ features=XX] + + If "features" exists, its value is a long. + """ if self._proxy_version is None and proxy_header is not None: versions = proxy_header.split() # bail if not of correct format - if len(versions) == 2: + if len(versions) >= 2: self._proxy_version = versions[0].split('=')[1] self._kv_version = versions[1].split('=')[1] + if (len(versions) >= 3 and + versions[2].split('=')[0] == 'features' and + len(versions[2].split('=')[1]) <= 16 ): + feat_str = versions[2].split('=')[1] + try: + self._features = int(feat_str) + except ValueError as e: + self._logutils.log_info( + f"Received invalid features flag from server: {feat_str}") + + def is_feature_enabled(self, feature_flag): + if self._proxy_version is None: + # there were no requests until now + request_utils = RequestUtils( + self._sess, self._logutils, None, self._retry_handler, + self, self._rate_limiter_map) + request_utils.do_head_request(self._request_uri, {}, + self._config.get_default_timeout()) + + return (self._features & feature_flag) != 0 diff --git a/src/borneo/http.py b/src/borneo/http.py index c1e23b8..b5a3909 100644 --- a/src/borneo/http.py +++ b/src/borneo/http.py @@ -172,6 +172,28 @@ def do_put_request(self, uri, headers, payload, timeout_ms): """ return self._do_request('PUT', uri, headers, payload, timeout_ms, None) + def do_head_request(self, uri, headers, timeout_ms): + """ + Issue HTTP HEAD request with retries and general error handling. + + It retries upon seeing following exceptions and response codes: + + HTTP response with status code larger than 500\n + Other throwable excluding RuntimeException, InterruptedException, + ExecutionException and TimeoutException + + :param uri: the request URI. + :type uri: str + :param headers: HTTP headers of this request. + :type headers: dict + :param timeout_ms: request timeout in milliseconds. + :type timeout_ms: int + :returns: HTTP response, a object encapsulate status code and response. + :rtype: HttpResponse or Result + """ + return self._do_request('HEAD', uri, headers, None, timeout_ms, None) + + def _do_request(self, method, uri, headers, payload, timeout_ms, stats_config): exception = None diff --git a/test/put.py b/test/put.py index 2942953..256ade1 100644 --- a/test/put.py +++ b/test/put.py @@ -10,6 +10,8 @@ from collections import OrderedDict from copy import deepcopy from dateutil import tz + +from borneo.client import Client from parameters import table_prefix from time import time @@ -193,6 +195,7 @@ def testPutGets(self): identity_cache_size) self.assertTrue(self.put_request.get_return_row()) + def testPutLastWriteMetadataAndCreationTime(self): last_write_meta = {'a':1, 'b':[0, "str", None, [], {}, True, False]} self.put_request.set_return_row(True)\ @@ -200,43 +203,55 @@ def testPutLastWriteMetadataAndCreationTime(self): self.assertEqual(self.put_request.get_last_write_metadata(), last_write_meta) - result = None + creation_time = time() * 1000 - if not self.feature_last_write_metadata_is_enabled: - try: - result = self.handle.put(self.put_request) - self.fail("handle.put(last_write_metadata) should have failed") - except IllegalArgumentException: - self.assertTrue(True) - else: + try: result = self.handle.put(self.put_request) + if not (self.handle.get_client() + .is_feature_enabled( + Client.FEATURE_FLAG_LAST_WRITE_METADATA)): + self.fail("handle.put(last_write_metadata) should have failed") - self.assertIsNotNone(result) - self.assertEqual(result.get_existing_last_write_metadata(), None) - self.assertEqual(result.get_existing_creation_time(), 0) - - result = self.handle.get(self.get_request) - self.assertIsNotNone(result) - self.assertEqual(result.get_last_write_metadata(), last_write_meta) + else: + self.assertIsNotNone(result) + self.assertEqual(result.get_existing_last_write_metadata(), None) + self.assertEqual(result.get_existing_creation_time(), 0) - last_write_meta2 = {'abc': 123} - self.put_request.set_return_row(True) \ - .set_last_write_metadata(last_write_meta2) - result = self.handle.put(self.put_request) + result = self.handle.get(self.get_request) + self.assertIsNotNone(result) + self.assertEqual(result.get_last_write_metadata(), last_write_meta) - self.assertIsNotNone(result) - self.assertEqual(result.get_existing_last_write_metadata(), - last_write_meta) + last_write_meta2 = {'abc': 123} + self.put_request.set_return_row(True) \ + .set_last_write_metadata(last_write_meta2) + result = self.handle.put(self.put_request) - result = self.handle.get(self.get_request) - self.assertIsNotNone(result) - self.assertEqual(result.get_last_write_metadata(), last_write_meta2) + self.assertIsNotNone(result) + self.assertEqual(result.get_existing_last_write_metadata(), + last_write_meta) + + result = self.handle.get(self.get_request) + self.assertIsNotNone(result) + self.assertEqual(result.get_last_write_metadata(), last_write_meta2) + + # check creation time too + if not (self.handle.get_client() + .is_feature_enabled(Client.FEATURE_FLAG_CREATION_TIME)): + self.assertEqual(result.get_creation_time(), 0) + else: + self.assertGreater(result.get_creation_time(), 1) + self.assertAlmostEqual(result.get_creation_time(), creation_time, delta=1000) + + except IllegalArgumentException: + if not (self.handle.get_client() + .is_feature_enabled( + Client.FEATURE_FLAG_LAST_WRITE_METADATA)): + # feature was not enabled so the put should have thrown + self.assertTrue(True) + else: + # feature was not enabled so the put should have NOT thrown + self.assertTrue(False) - # check creation time too - if not self.feature_creation_time_is_enabled: - self.assertEqual(result.get_creation_time(), 0) - else: - self.assertGreater(result.get_creation_time(), 1) def testPutIllegalRequest(self): self.assertRaises(IllegalArgumentException, self.handle.put, From b7c3a76bc58e027b09869d005e1642714e6b5308 Mon Sep 17 00:00:00 2001 From: Cezar Andrei Date: Fri, 20 Feb 2026 14:26:42 -0600 Subject: [PATCH 3/6] Removed the creation time feature flag. --- src/borneo/client.py | 7 +++---- src/borneo/http.py | 3 ++- test/put.py | 11 ++++------- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/borneo/client.py b/src/borneo/client.py index 17f7132..bdff9bc 100644 --- a/src/borneo/client.py +++ b/src/borneo/client.py @@ -37,7 +37,6 @@ class Client(object): # features flag bits FEATURE_FLAG_LAST_WRITE_METADATA = 1 << 0; - FEATURE_FLAG_CREATION_TIME = 1 << 1; # The HTTP driver client. def __init__(self, config, logger): @@ -601,7 +600,7 @@ def set_proxy_info(self, proxy_header): Format of the server version header string: proxy=X.Y.Z kv=X.Y.Z[ features=XX] - If "features" exists, its value is a long. + If "features" exists, its value is a long in hex. """ if self._proxy_version is None and proxy_header is not None: versions = proxy_header.split() @@ -614,8 +613,8 @@ def set_proxy_info(self, proxy_header): len(versions[2].split('=')[1]) <= 16 ): feat_str = versions[2].split('=')[1] try: - self._features = int(feat_str) - except ValueError as e: + self._features = int(feat_str, base=16) + except ValueError: self._logutils.log_info( f"Received invalid features flag from server: {feat_str}") diff --git a/src/borneo/http.py b/src/borneo/http.py index b5a3909..11c9189 100644 --- a/src/borneo/http.py +++ b/src/borneo/http.py @@ -312,9 +312,10 @@ def _do_request(self, method, uri, headers, payload, timeout_ms, self._logutils.log_debug( 'Response: ' + self._request.__class__.__name__ + ', status: ' + str(response.status_code)) - if self._request is not None: + if self._client is not None: self._client.set_proxy_info( response.headers.get(HttpConstants.RESPONSE_PROXY_INFO)) + if self._request is not None: res = self._process_response( self._request, response.content, response.status_code) # set server's serial version if available diff --git a/test/put.py b/test/put.py index 256ade1..45a6e40 100644 --- a/test/put.py +++ b/test/put.py @@ -234,13 +234,10 @@ def testPutLastWriteMetadataAndCreationTime(self): self.assertIsNotNone(result) self.assertEqual(result.get_last_write_metadata(), last_write_meta2) - # check creation time too - if not (self.handle.get_client() - .is_feature_enabled(Client.FEATURE_FLAG_CREATION_TIME)): - self.assertEqual(result.get_creation_time(), 0) - else: - self.assertGreater(result.get_creation_time(), 1) - self.assertAlmostEqual(result.get_creation_time(), creation_time, delta=1000) + # check creation time too, since no flag exists it can be 0 or near now + db_creation_time = result.get_creation_time() + self.assertTrue(db_creation_time == 0 or + (db_creation_time - creation_time) < 1000) except IllegalArgumentException: if not (self.handle.get_client() From a26b5561f8dc1e95e7b9eeb67c8f0d792f5c16ad Mon Sep 17 00:00:00 2001 From: Cezar Andrei Date: Fri, 20 Feb 2026 14:29:51 -0600 Subject: [PATCH 4/6] Update copyright to 2026. --- LICENSE.txt | 2 +- README.md | 4 ++-- examples/config.py | 2 +- examples/config_cloud.py | 2 +- examples/config_cloudsim.py | 2 +- examples/config_onprem.py | 2 +- examples/multi_data_ops.py | 2 +- examples/parameters.py | 2 +- examples/rate_limiting.py | 2 +- examples/single_data_ops.py | 2 +- examples/table_ops.py | 2 +- examples/utils.py | 2 +- src/borneo/__init__.py | 2 +- src/borneo/auth.py | 2 +- src/borneo/client.py | 2 +- src/borneo/common.py | 2 +- src/borneo/config.py | 2 +- src/borneo/driver.py | 2 +- src/borneo/exception.py | 2 +- src/borneo/http.py | 2 +- src/borneo/iam/__init__.py | 2 +- src/borneo/iam/iam.py | 2 +- src/borneo/kv/__init__.py | 2 +- src/borneo/kv/exception.py | 2 +- src/borneo/kv/kv.py | 2 +- src/borneo/operations.py | 2 +- src/borneo/query.py | 2 +- src/borneo/serde.py | 2 +- src/borneo/serdeutil.py | 2 +- src/borneo/stats.py | 2 +- src/borneo/version.py | 2 +- test/config.py | 2 +- test/config_cloudsim.py | 2 +- test/config_onprem.py | 2 +- test/delete.py | 2 +- test/field_range.py | 2 +- test/get.py | 2 +- test/get_indexes.py | 2 +- test/get_table.py | 2 +- test/handle_config.py | 2 +- test/list_tables.py | 2 +- test/multi_delete.py | 2 +- test/parameters.py | 2 +- test/prepare.py | 2 +- test/put.py | 2 +- test/query.py | 2 +- test/rate_limiting.py | 2 +- test/signature_provider.py | 2 +- test/stats.py | 2 +- test/store_at_provider.py | 2 +- test/system_request.py | 2 +- test/system_status.py | 2 +- test/table_limits.py | 2 +- test/table_request.py | 2 +- test/table_usage.py | 2 +- test/test_base.py | 2 +- test/testutils.py | 2 +- test/ttl.py | 2 +- test/write_multiple.py | 2 +- 59 files changed, 60 insertions(+), 60 deletions(-) diff --git a/LICENSE.txt b/LICENSE.txt index 327d6a7..28f849e 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,4 +1,4 @@ -Copyright (c) 2018, 2025 Oracle and/or its affiliates. +Copyright (c) 2018, 2026 Oracle and/or its affiliates. The Universal Permissive License (UPL), Version 1.0 diff --git a/README.md b/README.md index f11097d..253ae8d 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ that the *borneo* package has been installed. ``` python # -# Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026 Oracle and/or its affiliates. All rights reserved. # # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl/ @@ -426,7 +426,7 @@ Please consult the [security guide](./SECURITY.md) for our responsible security ## License -Copyright (c) 2018, 2025 Oracle and/or its affiliates. +Copyright (c) 2018, 2026 Oracle and/or its affiliates. Released under the Universal Permissive License v1.0 as shown at . diff --git a/examples/config.py b/examples/config.py index c126194..bbf63d8 100644 --- a/examples/config.py +++ b/examples/config.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026 Oracle and/or its affiliates. All rights reserved. # # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl/ diff --git a/examples/config_cloud.py b/examples/config_cloud.py index 43d1adc..4338813 100644 --- a/examples/config_cloud.py +++ b/examples/config_cloud.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026 Oracle and/or its affiliates. All rights reserved. # # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl/ diff --git a/examples/config_cloudsim.py b/examples/config_cloudsim.py index 4d57722..2275f88 100644 --- a/examples/config_cloudsim.py +++ b/examples/config_cloudsim.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026 Oracle and/or its affiliates. All rights reserved. # # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl/ diff --git a/examples/config_onprem.py b/examples/config_onprem.py index ac95e84..f0e5e31 100644 --- a/examples/config_onprem.py +++ b/examples/config_onprem.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026 Oracle and/or its affiliates. All rights reserved. # # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl/ diff --git a/examples/multi_data_ops.py b/examples/multi_data_ops.py index 0d0566a..d5985ee 100644 --- a/examples/multi_data_ops.py +++ b/examples/multi_data_ops.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026 Oracle and/or its affiliates. All rights reserved. # # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl/ diff --git a/examples/parameters.py b/examples/parameters.py index 55627ec..4c4a21d 100644 --- a/examples/parameters.py +++ b/examples/parameters.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026 Oracle and/or its affiliates. All rights reserved. # # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl/ diff --git a/examples/rate_limiting.py b/examples/rate_limiting.py index ab04d64..f139da6 100644 --- a/examples/rate_limiting.py +++ b/examples/rate_limiting.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026 Oracle and/or its affiliates. All rights reserved. # # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl/ diff --git a/examples/single_data_ops.py b/examples/single_data_ops.py index fe1c276..8996b6d 100644 --- a/examples/single_data_ops.py +++ b/examples/single_data_ops.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026 Oracle and/or its affiliates. All rights reserved. # # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl/ diff --git a/examples/table_ops.py b/examples/table_ops.py index dd7e515..db7de82 100644 --- a/examples/table_ops.py +++ b/examples/table_ops.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026 Oracle and/or its affiliates. All rights reserved. # # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl/ diff --git a/examples/utils.py b/examples/utils.py index 71b9496..08a14ef 100644 --- a/examples/utils.py +++ b/examples/utils.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026 Oracle and/or its affiliates. All rights reserved. # # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl/ diff --git a/src/borneo/__init__.py b/src/borneo/__init__.py index 3a695c0..8d32616 100644 --- a/src/borneo/__init__.py +++ b/src/borneo/__init__.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026 Oracle and/or its affiliates. All rights reserved. # # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl/ diff --git a/src/borneo/auth.py b/src/borneo/auth.py index 31d9054..c271828 100644 --- a/src/borneo/auth.py +++ b/src/borneo/auth.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026 Oracle and/or its affiliates. All rights reserved. # # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl/ diff --git a/src/borneo/client.py b/src/borneo/client.py index bdff9bc..f7b2722 100644 --- a/src/borneo/client.py +++ b/src/borneo/client.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026 Oracle and/or its affiliates. All rights reserved. # # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl/ diff --git a/src/borneo/common.py b/src/borneo/common.py index e089246..590a24f 100644 --- a/src/borneo/common.py +++ b/src/borneo/common.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026 Oracle and/or its affiliates. All rights reserved. # # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl/ diff --git a/src/borneo/config.py b/src/borneo/config.py index 9f3cb21..78016e4 100644 --- a/src/borneo/config.py +++ b/src/borneo/config.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026 Oracle and/or its affiliates. All rights reserved. # # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl/ diff --git a/src/borneo/driver.py b/src/borneo/driver.py index 02829fb..d798153 100644 --- a/src/borneo/driver.py +++ b/src/borneo/driver.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026 Oracle and/or its affiliates. All rights reserved. # # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl/ diff --git a/src/borneo/exception.py b/src/borneo/exception.py index 94329b1..e726c48 100644 --- a/src/borneo/exception.py +++ b/src/borneo/exception.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026 Oracle and/or its affiliates. All rights reserved. # # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl/ diff --git a/src/borneo/http.py b/src/borneo/http.py index 11c9189..40be27d 100644 --- a/src/borneo/http.py +++ b/src/borneo/http.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026 Oracle and/or its affiliates. All rights reserved. # # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl/ diff --git a/src/borneo/iam/__init__.py b/src/borneo/iam/__init__.py index fe43c8c..1ab68ae 100644 --- a/src/borneo/iam/__init__.py +++ b/src/borneo/iam/__init__.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026 Oracle and/or its affiliates. All rights reserved. # # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl/ diff --git a/src/borneo/iam/iam.py b/src/borneo/iam/iam.py index 3f25ef4..d549d7f 100644 --- a/src/borneo/iam/iam.py +++ b/src/borneo/iam/iam.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026 Oracle and/or its affiliates. All rights reserved. # # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl/ diff --git a/src/borneo/kv/__init__.py b/src/borneo/kv/__init__.py index d63e837..404f788 100644 --- a/src/borneo/kv/__init__.py +++ b/src/borneo/kv/__init__.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026 Oracle and/or its affiliates. All rights reserved. # # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl/ diff --git a/src/borneo/kv/exception.py b/src/borneo/kv/exception.py index 0f680cf..ac25aa6 100644 --- a/src/borneo/kv/exception.py +++ b/src/borneo/kv/exception.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026 Oracle and/or its affiliates. All rights reserved. # # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl/ diff --git a/src/borneo/kv/kv.py b/src/borneo/kv/kv.py index 21b4e02..8feaba8 100644 --- a/src/borneo/kv/kv.py +++ b/src/borneo/kv/kv.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026 Oracle and/or its affiliates. All rights reserved. # # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl/ diff --git a/src/borneo/operations.py b/src/borneo/operations.py index 8a2eb60..0504be1 100644 --- a/src/borneo/operations.py +++ b/src/borneo/operations.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026 Oracle and/or its affiliates. All rights reserved. # # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl/ diff --git a/src/borneo/query.py b/src/borneo/query.py index 5ac96fc..6b9755d 100644 --- a/src/borneo/query.py +++ b/src/borneo/query.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026 Oracle and/or its affiliates. All rights reserved. # # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl/ diff --git a/src/borneo/serde.py b/src/borneo/serde.py index 8c70234..b50e4dc 100644 --- a/src/borneo/serde.py +++ b/src/borneo/serde.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026 Oracle and/or its affiliates. All rights reserved. # # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl/ diff --git a/src/borneo/serdeutil.py b/src/borneo/serdeutil.py index beb5b53..bb001d0 100644 --- a/src/borneo/serdeutil.py +++ b/src/borneo/serdeutil.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026 Oracle and/or its affiliates. All rights reserved. # # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl/ diff --git a/src/borneo/stats.py b/src/borneo/stats.py index d269a47..fbaa1b6 100644 --- a/src/borneo/stats.py +++ b/src/borneo/stats.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026 Oracle and/or its affiliates. All rights reserved. # # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl/ diff --git a/src/borneo/version.py b/src/borneo/version.py index 7181bcb..a2589d2 100644 --- a/src/borneo/version.py +++ b/src/borneo/version.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026 Oracle and/or its affiliates. All rights reserved. # # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl/ diff --git a/test/config.py b/test/config.py index b429581..af023bf 100644 --- a/test/config.py +++ b/test/config.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026 Oracle and/or its affiliates. All rights reserved. # # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl/ diff --git a/test/config_cloudsim.py b/test/config_cloudsim.py index 8f8838c..ac1d3c7 100644 --- a/test/config_cloudsim.py +++ b/test/config_cloudsim.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026 Oracle and/or its affiliates. All rights reserved. # # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl/ diff --git a/test/config_onprem.py b/test/config_onprem.py index 41a1895..35afe13 100644 --- a/test/config_onprem.py +++ b/test/config_onprem.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026 Oracle and/or its affiliates. All rights reserved. # # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl/ diff --git a/test/delete.py b/test/delete.py index dd765d2..614ccf7 100644 --- a/test/delete.py +++ b/test/delete.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026 Oracle and/or its affiliates. All rights reserved. # # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl/ diff --git a/test/field_range.py b/test/field_range.py index d540861..4fee1e3 100644 --- a/test/field_range.py +++ b/test/field_range.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026 Oracle and/or its affiliates. All rights reserved. # # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl/ diff --git a/test/get.py b/test/get.py index feb45fc..3c0e88c 100644 --- a/test/get.py +++ b/test/get.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026 Oracle and/or its affiliates. All rights reserved. # # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl/ diff --git a/test/get_indexes.py b/test/get_indexes.py index ec396fb..fb51325 100644 --- a/test/get_indexes.py +++ b/test/get_indexes.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026 Oracle and/or its affiliates. All rights reserved. # # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl/ diff --git a/test/get_table.py b/test/get_table.py index a62a8b2..c41560b 100644 --- a/test/get_table.py +++ b/test/get_table.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026 Oracle and/or its affiliates. All rights reserved. # # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl/ diff --git a/test/handle_config.py b/test/handle_config.py index 6c99fe1..839d2c7 100644 --- a/test/handle_config.py +++ b/test/handle_config.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026 Oracle and/or its affiliates. All rights reserved. # # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl/ diff --git a/test/list_tables.py b/test/list_tables.py index 8d82663..28de822 100644 --- a/test/list_tables.py +++ b/test/list_tables.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026 Oracle and/or its affiliates. All rights reserved. # # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl/ diff --git a/test/multi_delete.py b/test/multi_delete.py index 7054f4e..b43158f 100644 --- a/test/multi_delete.py +++ b/test/multi_delete.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026 Oracle and/or its affiliates. All rights reserved. # # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl/ diff --git a/test/parameters.py b/test/parameters.py index c011f46..ce37f90 100644 --- a/test/parameters.py +++ b/test/parameters.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026 Oracle and/or its affiliates. All rights reserved. # # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl/ diff --git a/test/prepare.py b/test/prepare.py index a4b5e45..df23b48 100644 --- a/test/prepare.py +++ b/test/prepare.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026 Oracle and/or its affiliates. All rights reserved. # # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl/ diff --git a/test/put.py b/test/put.py index 45a6e40..21ee7fe 100644 --- a/test/put.py +++ b/test/put.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026 Oracle and/or its affiliates. All rights reserved. # # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl/ diff --git a/test/query.py b/test/query.py index b9da386..de94189 100644 --- a/test/query.py +++ b/test/query.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026 Oracle and/or its affiliates. All rights reserved. # # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl/ diff --git a/test/rate_limiting.py b/test/rate_limiting.py index c4ec792..b381bd9 100644 --- a/test/rate_limiting.py +++ b/test/rate_limiting.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026 Oracle and/or its affiliates. All rights reserved. # # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl/ diff --git a/test/signature_provider.py b/test/signature_provider.py index f56b92d..f66c6e1 100644 --- a/test/signature_provider.py +++ b/test/signature_provider.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026 Oracle and/or its affiliates. All rights reserved. # # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl/ diff --git a/test/stats.py b/test/stats.py index 2c1c92f..85304bc 100644 --- a/test/stats.py +++ b/test/stats.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026 Oracle and/or its affiliates. All rights reserved. # # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl/ diff --git a/test/store_at_provider.py b/test/store_at_provider.py index 2bf2847..f2355b0 100644 --- a/test/store_at_provider.py +++ b/test/store_at_provider.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026 Oracle and/or its affiliates. All rights reserved. # # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl/ diff --git a/test/system_request.py b/test/system_request.py index e88213d..bbc73fd 100644 --- a/test/system_request.py +++ b/test/system_request.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026 Oracle and/or its affiliates. All rights reserved. # # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl/ diff --git a/test/system_status.py b/test/system_status.py index 8f3de09..3579288 100644 --- a/test/system_status.py +++ b/test/system_status.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026 Oracle and/or its affiliates. All rights reserved. # # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl/ diff --git a/test/table_limits.py b/test/table_limits.py index f5a374d..219b577 100644 --- a/test/table_limits.py +++ b/test/table_limits.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026 Oracle and/or its affiliates. All rights reserved. # # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl/ diff --git a/test/table_request.py b/test/table_request.py index f930719..d67f717 100644 --- a/test/table_request.py +++ b/test/table_request.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026 Oracle and/or its affiliates. All rights reserved. # # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl/ diff --git a/test/table_usage.py b/test/table_usage.py index 65c2737..ed18edf 100644 --- a/test/table_usage.py +++ b/test/table_usage.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026 Oracle and/or its affiliates. All rights reserved. # # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl/ diff --git a/test/test_base.py b/test/test_base.py index 32746d0..6548b1b 100644 --- a/test/test_base.py +++ b/test/test_base.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026 Oracle and/or its affiliates. All rights reserved. # # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl/ diff --git a/test/testutils.py b/test/testutils.py index 738e960..46c2c33 100644 --- a/test/testutils.py +++ b/test/testutils.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026 Oracle and/or its affiliates. All rights reserved. # # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl/ diff --git a/test/ttl.py b/test/ttl.py index 6a1d013..b6fd33f 100644 --- a/test/ttl.py +++ b/test/ttl.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026 Oracle and/or its affiliates. All rights reserved. # # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl/ diff --git a/test/write_multiple.py b/test/write_multiple.py index 2dd1695..4b14971 100644 --- a/test/write_multiple.py +++ b/test/write_multiple.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026 Oracle and/or its affiliates. All rights reserved. # # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl/ From d8b5476ecea690ac544c67e93136538d830b820a Mon Sep 17 00:00:00 2001 From: Cezar Andrei Date: Fri, 20 Feb 2026 14:31:52 -0600 Subject: [PATCH 5/6] Revert config.py files. --- examples/config.py | 4 ++-- test/config.py | 42 ++++++++++++++++-------------------------- 2 files changed, 18 insertions(+), 28 deletions(-) diff --git a/examples/config.py b/examples/config.py index bbf63d8..ececf2c 100644 --- a/examples/config.py +++ b/examples/config.py @@ -19,7 +19,7 @@ # The endpoint to use to connect to the service. This endpoint is for a Cloud # Simulator running on its default port (8080) on the local machine. -endpoint = 'http://localhost:8080' +endpoint = 'localhost:8080' # The server type. -server_type = 'onprem' +server_type = 'cloudsim' diff --git a/test/config.py b/test/config.py index af023bf..8f8838c 100644 --- a/test/config.py +++ b/test/config.py @@ -1,40 +1,30 @@ # -# Copyright (c) 2018, 2026 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. # # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl/ -# # -# Parameters used by test code -- On-premise Oracle NoSQL database +# Parameters used by test code -- Cloud Simulator Configuration # -# This file is configured for the unit tests to be run against a On-prem Oracle -# NoSQL database. Please start the database and proxy first. +# This file is configured for the unit tests to be run against a Cloud Simulator +# instance. The simulator is used so that limits that exist in the cloud service +# are not involved and there is no cost involved in running the unit tests. # -# The default settings below are sufficient if the On-prem proxy has been -# started on the endpoint, localhost:8080, with security disable. If not, the -# parameters in this file should be changed as needed. +# The default settings below are sufficient if the Cloud Simulator has been +# started on the endpoint, localhost:8080, which is its default. If not, the +# parameters in this file should be changed as needed. This is a backup of +# config.py, when config.py is overwritten by other config*.py, the default +# config.py of Cloud Simulator is back up in this file. # -# The endpoint to use to connect to the service. This endpoint is for a on-prem -# proxy started by the customer. Use 'http' protocol for non-secure database and -# 'https' for secure database. -endpoint = 'http://localhost:8080' +# The endpoint to use to connect to the service. This endpoint is for a Cloud +# Simulator running on its default port (8080) on the local machine. +endpoint = 'localhost:8080' # The server type, please don't change it. -server_type = 'onprem' +server_type = 'cloudsim' -# On-prem Oracle NoSQL database version. Use None to test with the latest NoSQL -# Database version, a specified version should be like "20.2.0". +# Cloud Simulator version. Use None to test with the latest Cloud Simulator +# version, a specified version should be like "1.4.0". version = None - -# Please set the following parameters if running against secure database. - -# SSL CA certificates. Configure it to specify CA certificates or set -# REQUESTS_CA_BUNDLE environment variable when running against a secure -# database. For non-secure database, use the default None. -ca_certs = None -# User name for secure database, for non-secure database, use the default None. -user_name = None -# Password for secure database, for non-secure database, use the default None. -user_password = None From 42d571fca48ac84f477fb7b1e1d1605b3b290c01 Mon Sep 17 00:00:00 2001 From: Cezar Andrei Date: Tue, 10 Mar 2026 14:39:18 -0500 Subject: [PATCH 6/6] Added check of enabled LWM on execute request. Updated tests. --- src/borneo/client.py | 15 +++++++++++++-- src/borneo/operations.py | 6 ++++++ test/put.py | 39 ++++++++++++++++++++++++++++++--------- 3 files changed, 49 insertions(+), 11 deletions(-) diff --git a/src/borneo/client.py b/src/borneo/client.py index f7b2722..d2fd4e4 100644 --- a/src/borneo/client.py +++ b/src/borneo/client.py @@ -22,6 +22,7 @@ OperationNotSupportedException, RequestSizeLimitException) from .http import RateLimiterMap, RequestUtils from .kv import StoreAccessTokenProvider +from .nson_protocol import LAST_WRITE_METADATA from .operations import ( GetTableRequest, QueryRequest, QueryResult, TableRequest, WriteRequest) from .query import QueryDriver @@ -35,8 +36,9 @@ class Client(object): LIMITER_REFRESH_NANOS = 600000000000 TRACE_LEVEL = 0 - # features flag bits - FEATURE_FLAG_LAST_WRITE_METADATA = 1 << 0; + # proxy enabled features flag bits, works on this._features + # Features: added in KV 26.1, SDK 5.4.4 + FEATURE_FLAG_LAST_WRITE_METADATA = 1 << 0 # The HTTP driver client. def __init__(self, config, logger): @@ -120,6 +122,8 @@ def __init__(self, config, logger): self._stats_control = StatsControl(config, logger, config.get_rate_limiting_enabled()) + # Keeps a set of bits each one corresponding to an enabled feature + # signaled by the httpproxy. See FEATURE_FLAG_LAST_WRITE_METADATA. self._features = 0 @synchronized @@ -182,6 +186,13 @@ def execute(self, request): CheckValue.check_not_none(request, 'request') request.set_defaults(self._config) request.validate() + + if (request.get_last_write_metadata() is not None) and \ + (not self.is_feature_enabled( + self.FEATURE_FLAG_LAST_WRITE_METADATA)): + raise OperationNotSupportedException('Last Write Metadata is not' + + 'supported on this server') + if request.is_query_request(): self._stats_control.observe_query(request) diff --git a/src/borneo/operations.py b/src/borneo/operations.py index 0504be1..27d31f1 100644 --- a/src/borneo/operations.py +++ b/src/borneo/operations.py @@ -439,6 +439,12 @@ def set_topo_seq_num(self, num): def get_topo_seq_num(self): return self._topo_seq_num + def get_last_write_metadata(self): + """ + Internal use only. + """ + return None + class WriteRequest(Request): """ diff --git a/test/put.py b/test/put.py index 21ee7fe..de011c8 100644 --- a/test/put.py +++ b/test/put.py @@ -196,15 +196,13 @@ def testPutGets(self): self.assertTrue(self.put_request.get_return_row()) - def testPutLastWriteMetadataAndCreationTime(self): + def testPutLastWriteMetadata(self): last_write_meta = {'a':1, 'b':[0, "str", None, [], {}, True, False]} self.put_request.set_return_row(True)\ .set_last_write_metadata(last_write_meta) self.assertEqual(self.put_request.get_last_write_metadata(), last_write_meta) - creation_time = time() * 1000 - try: result = self.handle.put(self.put_request) if not (self.handle.get_client() @@ -215,7 +213,6 @@ def testPutLastWriteMetadataAndCreationTime(self): else: self.assertIsNotNone(result) self.assertEqual(result.get_existing_last_write_metadata(), None) - self.assertEqual(result.get_existing_creation_time(), 0) result = self.handle.get(self.get_request) self.assertIsNotNone(result) @@ -234,11 +231,6 @@ def testPutLastWriteMetadataAndCreationTime(self): self.assertIsNotNone(result) self.assertEqual(result.get_last_write_metadata(), last_write_meta2) - # check creation time too, since no flag exists it can be 0 or near now - db_creation_time = result.get_creation_time() - self.assertTrue(db_creation_time == 0 or - (db_creation_time - creation_time) < 1000) - except IllegalArgumentException: if not (self.handle.get_client() .is_feature_enabled( @@ -250,6 +242,35 @@ def testPutLastWriteMetadataAndCreationTime(self): self.assertTrue(False) + def testPutCreationTime(self): + self.put_request.set_return_row(True) + + creation_time = time() * 1000 + + result = self.handle.put(self.put_request) + self.assertIsNotNone(result) + + # check creation time, since no flag exists it can be 0 or near now + self.assertTrue(result.get_existing_creation_time() == 0 or + result.get_existing_creation_time() - creation_time < 1000) + + result = self.handle.get(self.get_request) + self.assertIsNotNone(result) + + self.put_request.set_return_row(True) + result = self.handle.put(self.put_request) + + self.assertIsNotNone(result) + + result = self.handle.get(self.get_request) + self.assertIsNotNone(result) + + # check creation time, since no flag exists it can be 0 or near now + db_creation_time = result.get_creation_time() + self.assertTrue(db_creation_time == 0 or + (db_creation_time - creation_time) < 1000) + + def testPutIllegalRequest(self): self.assertRaises(IllegalArgumentException, self.handle.put, 'IllegalRequest')