Skip to content

Commit 98a9db4

Browse files
Refactored encoding of Oracle data types; fixed bug when binding values
of type datetime.date to timestamp variables.
1 parent 1390bab commit 98a9db4

File tree

12 files changed

+431
-300
lines changed

12 files changed

+431
-300
lines changed

doc/src/release_notes.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ oracledb `3.4.0 <https://github.com/oracle/python-oracledb/compare/v3.3.0...v3.4
1919
Thin Mode Changes
2020
+++++++++++++++++
2121

22+
#) Fixed bug when setting values of type ``datetime.date`` on variables of
23+
types :attr:`oracledb.DB_TYPE_TIMESTAMP`,
24+
:attr:`oracledb.DB_TYPE_TIMESTAMP_TZ` and
25+
:attr:`oracledb.DB_TYPE_TIMESTAMP_LTZ`.
26+
#) Internal change: refactor encoding of Oracle data types.
2227
#) Internal change: small performance improvement sending bytes on the
2328
network transport.
2429

src/oracledb/base_impl.pxd

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -313,19 +313,14 @@ cdef class Buffer:
313313
cdef inline int skip_ub2(self) except -1
314314
cdef inline int skip_ub4(self) except -1
315315
cdef inline int skip_ub8(self) except -1
316-
cdef int write_binary_double(self, double value,
317-
bint write_length=*) except -1
318-
cdef int write_binary_float(self, float value,
319-
bint write_length=*) except -1
316+
cdef int write_binary_double(self, double value) except -1
317+
cdef int write_binary_float(self, float value) except -1
320318
cdef int write_bool(self, bint value) except -1
321319
cdef int write_bytes(self, bytes value) except -1
322320
cdef int write_bytes_with_length(self, bytes value) except -1
323-
cdef int write_interval_ds(self, object value,
324-
bint write_length=*) except -1
325-
cdef int write_interval_ym(self, object value,
326-
bint write_length=*) except -1
327-
cdef int write_oracle_date(self, object value, uint8_t length,
328-
bint write_length=*) except -1
321+
cdef int write_interval_ds(self, object value) except -1
322+
cdef int write_interval_ym(self, object value) except -1
323+
cdef int write_oracle_date(self, object value, uint8_t length) except -1
329324
cdef int write_oracle_number(self, bytes num_bytes) except -1
330325
cdef int write_raw(self, const char_type *data, ssize_t length) except -1
331326
cdef int write_sb4(self, int32_t value) except -1

src/oracledb/impl/base/buffer.pyx

Lines changed: 40 additions & 270 deletions
Original file line numberDiff line numberDiff line change
@@ -473,91 +473,33 @@ cdef class Buffer:
473473
"""
474474
return self._skip_int(8, NULL)
475475

476-
cdef int write_binary_double(self, double value,
477-
bint write_length=True) except -1:
476+
cdef int write_binary_double(self, double value) except -1:
478477
"""
479478
Writes a double value to the buffer in Oracle canonical double floating
480479
point format.
481480
"""
482-
cdef:
483-
uint8_t b0, b1, b2, b3, b4, b5, b6, b7
484-
uint64_t all_bits
485-
char_type buf[8]
486-
uint64_t *ptr
487-
ptr = <uint64_t*> &value
488-
all_bits = ptr[0]
489-
b7 = all_bits & 0xff
490-
b6 = (all_bits >> 8) & 0xff
491-
b5 = (all_bits >> 16) & 0xff
492-
b4 = (all_bits >> 24) & 0xff
493-
b3 = (all_bits >> 32) & 0xff
494-
b2 = (all_bits >> 40) & 0xff
495-
b1 = (all_bits >> 48) & 0xff
496-
b0 = (all_bits >> 56) & 0xff
497-
if b0 & 0x80 == 0:
498-
b0 = b0 | 0x80
499-
else:
500-
b0 = ~b0
501-
b1 = ~b1
502-
b2 = ~b2
503-
b3 = ~b3
504-
b4 = ~b4
505-
b5 = ~b5
506-
b6 = ~b6
507-
b7 = ~b7
508-
buf[0] = b0
509-
buf[1] = b1
510-
buf[2] = b2
511-
buf[3] = b3
512-
buf[4] = b4
513-
buf[5] = b5
514-
buf[6] = b6
515-
buf[7] = b7
516-
if write_length:
517-
self.write_uint8(8)
518-
self.write_raw(buf, 8)
481+
cdef char_type buf[ORA_TYPE_SIZE_BINARY_DOUBLE]
482+
encode_binary_double(buf, value)
483+
self._write_raw_bytes_and_length(buf, sizeof(buf))
519484

520-
cdef int write_binary_float(self, float value,
521-
bint write_length=True) except -1:
485+
cdef int write_binary_float(self, float value) except -1:
522486
"""
523487
Writes a float value to the buffer in Oracle canonical floating point
524488
format.
525489
"""
526-
cdef:
527-
uint8_t b0, b1, b2, b3
528-
uint32_t all_bits
529-
char_type buf[4]
530-
uint32_t *ptr
531-
ptr = <uint32_t*> &value
532-
all_bits = ptr[0]
533-
b3 = all_bits & 0xff
534-
b2 = (all_bits >> 8) & 0xff
535-
b1 = (all_bits >> 16) & 0xff
536-
b0 = (all_bits >> 24) & 0xff
537-
if b0 & 0x80 == 0:
538-
b0 = b0 | 0x80
539-
else:
540-
b0 = ~b0
541-
b1 = ~b1
542-
b2 = ~b2
543-
b3 = ~b3
544-
buf[0] = b0
545-
buf[1] = b1
546-
buf[2] = b2
547-
buf[3] = b3
548-
if write_length:
549-
self.write_uint8(4)
550-
self.write_raw(buf, 4)
490+
cdef char_type buf[ORA_TYPE_SIZE_BINARY_FLOAT]
491+
encode_binary_float(buf, value)
492+
self._write_raw_bytes_and_length(buf, sizeof(buf))
551493

552494
cdef int write_bool(self, bint value) except -1:
553495
"""
554496
Writes a boolean value to the buffer.
555497
"""
556-
if value:
557-
self.write_uint8(2)
558-
self.write_uint16be(0x0101)
559-
else:
560-
self.write_uint16be(0x0100)
498+
cdef:
499+
char_type buf[ORA_TYPE_SIZE_BOOLEAN]
500+
ssize_t buflen
501+
encode_boolean(buf, &buflen, value)
502+
self._write_raw_bytes_and_length(buf, buflen)
561503

562504
cdef int write_bytes(self, bytes value) except -1:
563505
"""
@@ -579,223 +521,51 @@ cdef class Buffer:
579521
cpython.PyBytes_AsStringAndSize(value, <char**> &ptr, &value_len)
580522
self._write_raw_bytes_and_length(ptr, value_len)
581523

582-
cdef int write_interval_ds(self, object value,
583-
bint write_length=True) except -1:
524+
cdef int write_interval_ds(self, object value) except -1:
584525
"""
585526
Writes an interval to the buffer in Oracle Interval Day To Second
586527
format.
587528
"""
588-
cdef:
589-
int32_t days, seconds, fseconds
590-
char_type buf[11]
591-
days = cydatetime.timedelta_days(value)
592-
encode_uint32be(buf, days + TNS_DURATION_MID)
593-
seconds = cydatetime.timedelta_seconds(value)
594-
buf[4] = (seconds // 3600) + TNS_DURATION_OFFSET
595-
seconds = seconds % 3600
596-
buf[5] = (seconds // 60) + TNS_DURATION_OFFSET
597-
buf[6] = (seconds % 60) + TNS_DURATION_OFFSET
598-
fseconds = cydatetime.timedelta_microseconds(value) * 1000
599-
encode_uint32be(&buf[7], fseconds + TNS_DURATION_MID)
600-
if write_length:
601-
self.write_uint8(sizeof(buf))
602-
self.write_raw(buf, sizeof(buf))
603-
604-
cdef int write_interval_ym(self, object value,
605-
bint write_length=True) except -1:
529+
cdef char_type buf[ORA_TYPE_SIZE_INTERVAL_DS]
530+
encode_interval_ds(buf, value)
531+
self._write_raw_bytes_and_length(buf, sizeof(buf))
532+
533+
cdef int write_interval_ym(self, object value) except -1:
606534
"""
607-
Writes an interval to the buffer in Oracle Interval Day To Second
535+
Writes an interval to the buffer in Oracle Interval Year To Month
608536
format.
609537
"""
610-
cdef:
611-
int32_t years, months
612-
char_type buf[5]
613-
years = (<tuple> value)[0]
614-
months = (<tuple> value)[1]
615-
encode_uint32be(buf, years + TNS_DURATION_MID)
616-
buf[4] = months + TNS_DURATION_OFFSET
617-
if write_length:
618-
self.write_uint8(sizeof(buf))
619-
self.write_raw(buf, sizeof(buf))
620-
621-
cdef int write_oracle_date(self, object value, uint8_t length,
622-
bint write_length=True) except -1:
538+
cdef char_type buf[ORA_TYPE_SIZE_INTERVAL_YM]
539+
encode_interval_ym(buf, value)
540+
self._write_raw_bytes_and_length(buf, sizeof(buf))
541+
542+
cdef int write_oracle_date(self, object value, uint8_t length) except -1:
623543
"""
624544
Writes a date to the buffer in Oracle Date format.
625545
"""
626-
cdef:
627-
unsigned int year
628-
char_type buf[13]
629-
uint32_t fsecond
630-
year = cydatetime.PyDateTime_GET_YEAR(value)
631-
buf[0] = <uint8_t> ((year // 100) + 100)
632-
buf[1] = <uint8_t> ((year % 100) + 100)
633-
buf[2] = <uint8_t> cydatetime.PyDateTime_GET_MONTH(value)
634-
buf[3] = <uint8_t> cydatetime.PyDateTime_GET_DAY(value)
635-
buf[4] = <uint8_t> cydatetime.PyDateTime_DATE_GET_HOUR(value) + 1
636-
buf[5] = <uint8_t> cydatetime.PyDateTime_DATE_GET_MINUTE(value) + 1
637-
buf[6] = <uint8_t> cydatetime.PyDateTime_DATE_GET_SECOND(value) + 1
638-
if length > 7:
639-
fsecond = <uint32_t> \
640-
cydatetime.PyDateTime_DATE_GET_MICROSECOND(value) * 1000
641-
if fsecond == 0 and length <= 11:
546+
cdef char_type buf[ORA_TYPE_SIZE_TIMESTAMP_TZ]
547+
if length == 7:
548+
encode_date(buf, value)
549+
elif length == 11:
550+
encode_timestamp(buf, value)
551+
# the protocol requires that if the fractional seconds are zero
552+
# that the value be transmitted as a date, not a timestamp!
553+
if decode_uint32be(&buf[7]) == 0:
642554
length = 7
643-
else:
644-
encode_uint32be(&buf[7], fsecond)
645-
if length > 11:
646-
buf[11] = TZ_HOUR_OFFSET
647-
buf[12] = TZ_MINUTE_OFFSET
648-
if write_length:
649-
self.write_uint8(length)
650-
self.write_raw(buf, length)
555+
else:
556+
encode_timestamp_tz(buf, value)
557+
self._write_raw_bytes_and_length(buf, length)
651558

652559
cdef int write_oracle_number(self, bytes num_bytes) except -1:
653560
"""
654561
Writes a number in UTF-8 encoded bytes in Oracle Number format to the
655562
buffer.
656563
"""
657564
cdef:
658-
uint8_t num_digits = 0, digit, num_pairs, pair_num, digits_pos
659-
bint exponent_is_negative = False, append_sentinel = False
660-
ssize_t num_bytes_length, exponent_pos, pos = 0
661-
bint is_negative = False, prepend_zero = False
662-
uint8_t digits[NUMBER_AS_TEXT_CHARS]
663-
int16_t decimal_point_index
664-
int8_t exponent_on_wire
665-
const char_type *ptr
666-
int16_t exponent
667-
668-
# zero length string cannot be converted
669-
num_bytes_length = len(num_bytes)
670-
if num_bytes_length == 0:
671-
errors._raise_err(errors.ERR_NUMBER_STRING_OF_ZERO_LENGTH)
672-
elif num_bytes_length > NUMBER_AS_TEXT_CHARS:
673-
errors._raise_err(errors.ERR_NUMBER_STRING_TOO_LONG)
674-
675-
# check to see if number is negative (first character is '-')
676-
ptr = num_bytes
677-
if ptr[0] == b'-':
678-
is_negative = True
679-
pos += 1
680-
681-
# scan for digits until the decimal point or exponent indicator found
682-
while pos < num_bytes_length:
683-
if ptr[pos] == b'.' or ptr[pos] == b'e' or ptr[pos] == b'E':
684-
break
685-
if ptr[pos] < b'0' or ptr[pos] > b'9':
686-
errors._raise_err(errors.ERR_INVALID_NUMBER)
687-
digit = ptr[pos] - <uint8_t> b'0'
688-
pos += 1
689-
if digit == 0 and num_digits == 0:
690-
continue
691-
digits[num_digits] = digit
692-
num_digits += 1
693-
decimal_point_index = num_digits
694-
695-
# scan for digits following the decimal point, if applicable
696-
if pos < num_bytes_length and ptr[pos] == b'.':
697-
pos += 1
698-
while pos < num_bytes_length:
699-
if ptr[pos] == b'e' or ptr[pos] == b'E':
700-
break
701-
digit = ptr[pos] - <uint8_t> b'0'
702-
pos += 1
703-
if digit == 0 and num_digits == 0:
704-
decimal_point_index -= 1
705-
continue
706-
digits[num_digits] = digit
707-
num_digits += 1
708-
709-
# handle exponent, if applicable
710-
if pos < num_bytes_length and (ptr[pos] == b'e' or ptr[pos] == b'E'):
711-
pos += 1
712-
if pos < num_bytes_length:
713-
if ptr[pos] == b'-':
714-
exponent_is_negative = True
715-
pos += 1
716-
elif ptr[pos] == b'+':
717-
pos += 1
718-
exponent_pos = pos
719-
while pos < num_bytes_length:
720-
if ptr[pos] < b'0' or ptr[pos] > b'9':
721-
errors._raise_err(errors.ERR_NUMBER_WITH_INVALID_EXPONENT)
722-
pos += 1
723-
if exponent_pos == pos:
724-
errors._raise_err(errors.ERR_NUMBER_WITH_EMPTY_EXPONENT)
725-
exponent = <int16_t> int(ptr[exponent_pos:pos])
726-
if exponent_is_negative:
727-
exponent = -exponent
728-
decimal_point_index += exponent
729-
730-
# if there is anything left in the string, that indicates an invalid
731-
# number as well
732-
if pos < num_bytes_length:
733-
errors._raise_err(errors.ERR_CONTENT_INVALID_AFTER_NUMBER)
734-
735-
# skip trailing zeros
736-
while num_digits > 0 and digits[num_digits - 1] == 0:
737-
num_digits -= 1
738-
739-
# value must be less than 1e126 and greater than 1e-129; the number of
740-
# digits also cannot exceed the maximum precision of Oracle numbers
741-
if num_digits > NUMBER_MAX_DIGITS or decimal_point_index > 126 \
742-
or decimal_point_index < -129:
743-
errors._raise_err(errors.ERR_ORACLE_NUMBER_NO_REPR)
744-
745-
# if the exponent is odd, prepend a zero
746-
if decimal_point_index % 2 == 1:
747-
prepend_zero = True
748-
if num_digits > 0:
749-
digits[num_digits] = 0
750-
num_digits += 1
751-
decimal_point_index += 1
752-
753-
# determine the number of digit pairs; if the number of digits is odd,
754-
# append a zero to make the number of digits even
755-
if num_digits % 2 == 1:
756-
digits[num_digits] = 0
757-
num_digits += 1
758-
num_pairs = num_digits // 2
759-
760-
# append a sentinel 102 byte for negative numbers if there is room
761-
if is_negative and num_digits > 0 and num_digits < NUMBER_MAX_DIGITS:
762-
append_sentinel = True
763-
764-
# write length of number
765-
self.write_uint8(num_pairs + 1 + append_sentinel)
766-
767-
# if the number of digits is zero, the value is itself zero since all
768-
# leading and trailing zeros are removed from the digits string; this
769-
# is a special case
770-
if num_digits == 0:
771-
self.write_uint8(128)
772-
return 0
773-
774-
# write the exponent
775-
exponent_on_wire = <int8_t> (decimal_point_index / 2) + 192
776-
if is_negative:
777-
exponent_on_wire = ~exponent_on_wire
778-
self.write_uint8(exponent_on_wire)
779-
780-
# write the mantissa bytes
781-
digits_pos = 0
782-
for pair_num in range(num_pairs):
783-
if pair_num == 0 and prepend_zero:
784-
digit = digits[digits_pos]
785-
digits_pos += 1
786-
else:
787-
digit = digits[digits_pos] * 10 + digits[digits_pos + 1]
788-
digits_pos += 2
789-
if is_negative:
790-
digit = 101 - digit
791-
else:
792-
digit += 1
793-
self.write_uint8(digit)
794-
795-
# append 102 byte for negative numbers if the number of digits is less
796-
# than the maximum allowable
797-
if append_sentinel:
798-
self.write_uint8(102)
565+
char_type buf[ORA_TYPE_SIZE_NUMBER]
566+
ssize_t buflen
567+
encode_number(buf, &buflen, num_bytes)
568+
self._write_raw_bytes_and_length(buf, buflen)
799569

800570
cdef int write_raw(self, const char_type *data, ssize_t length) except -1:
801571
"""

0 commit comments

Comments
 (0)