Coverage for dj/sql/parsing/types.py: 92%

274 statements  

« prev     ^ index     » next       coverage.py v7.2.3, created at 2023-04-17 20:05 -0700

1"""DJ Column Types 

2 

3 Example: 

4 >>> StructType( \ 

5 NestedField("required_field", StringType(), False, "a required field"), \ 

6 NestedField("optional_field", IntegerType(), True, "an optional field") \ 

7 ) 

8 StructType(NestedField(name=Name(name='required_field', quote_style='', namespace=None), \ 

9field_type=StringType(), is_optional=False, doc='a required field'), \ 

10NestedField(name=Name(name='optional_field', quote_style='', namespace=None), \ 

11field_type=IntegerType(), is_optional=True, doc='an optional field')) 

12 

13""" 

14 

15import re 

16from enum import Enum 

17from typing import TYPE_CHECKING, Any, ClassVar, Dict, Generator, Optional, Tuple, cast 

18 

19from pydantic import BaseModel, Extra 

20from pydantic.class_validators import AnyCallable 

21 

22if TYPE_CHECKING: 

23 from dj.sql.parsing import ast 

24 

25 

26DECIMAL_REGEX = re.compile(r"(?i)decimal\((?P<precision>\d+),\s*(?P<scale>\d+)\)") 

27FIXED_PARSER = re.compile(r"(?i)fixed\((?P<length>\d+)\)") 

28 

29 

30class Singleton: # pylint: disable=too-few-public-methods 

31 """ 

32 Singleton for types 

33 """ 

34 

35 _instance = None 

36 

37 def __new__(cls, *args, **kwargs): # pylint: disable=unused-argument 

38 if not isinstance(cls._instance, cls): 

39 cls._instance = super(Singleton, cls).__new__(cls) 

40 return cls._instance 

41 

42 

43class ColumnType(BaseModel): 

44 """ 

45 Base type for all Column Types 

46 """ 

47 

48 _initialized = False 

49 

50 class Config: # pylint: disable=missing-class-docstring, too-few-public-methods 

51 extra = Extra.allow 

52 arbitrary_types_allowed = True 

53 underscore_attrs_are_private = False 

54 

55 def __init__( # pylint: disable=keyword-arg-before-vararg 

56 self, 

57 type_string: str, 

58 repr_string: str = None, 

59 *args, 

60 **kwargs, 

61 ): 

62 super().__init__(*args, **kwargs) 

63 self._type_string = type_string 

64 self._repr_string = repr_string if repr_string else self._type_string 

65 self._initialized = True 

66 

67 def __repr__(self): 

68 return self._repr_string 

69 

70 def __str__(self): 

71 return self._type_string 

72 

73 def __deepcopy__(self, memo): 

74 return self 

75 

76 @classmethod 

77 def __get_validators__(cls) -> Generator[AnyCallable, None, None]: 

78 """ 

79 One or more validators may be yielded which will be called in the 

80 order to validate the input, each validator will receive as an input 

81 the value returned from the previous validator 

82 """ 

83 yield cls.validate 

84 

85 @classmethod 

86 def validate( # pylint: disable=too-many-return-statements 

87 cls, 

88 v: Any, 

89 ) -> "ColumnType": 

90 """ 

91 Parses the column type 

92 """ 

93 from dj.sql.parsing.backends.antlr4 import ( # pylint: disable=import-outside-toplevel 

94 parse_rule, 

95 ) 

96 

97 return cast(ColumnType, parse_rule(str(v), "dataType")) 

98 

99 def __eq__(self, other: "ColumnType"): # type: ignore 

100 """ 

101 Equality is dependent on the string representation of the column type. 

102 """ 

103 return str(other) == str(self) 

104 

105 def __hash__(self): 

106 """ 

107 Equality is dependent on the string representation of the column type. 

108 """ 

109 return hash(str(self)) 

110 

111 def is_compatible(self, other: "ColumnType") -> bool: 

112 """ 

113 Returns whether the two types are compatible with each other by 

114 checking their ancestors. 

115 """ 

116 if self == other: 

117 return True # quick return 

118 

119 def has_common_ancestor(type1, type2) -> bool: 

120 """ 

121 Helper function to check whether two column types have common ancestors, 

122 other than the highest-level ancestor types like ColumnType itself. This 

123 determines whether they're part of the same type group and are compatible 

124 with each other when performing type compatibility checks. 

125 """ 

126 base_types = (ColumnType, Singleton, PrimitiveType) 

127 if type1 in base_types or type2 in base_types: 

128 return False 

129 if type1 == type2: 

130 return True 

131 current_has = False 

132 for ancestor in type1.__bases__: 

133 for ancestor2 in type2.__bases__: 

134 current_has = current_has or has_common_ancestor( 

135 ancestor, 

136 ancestor2, 

137 ) 

138 if current_has: 

139 return current_has 

140 return False 

141 

142 return has_common_ancestor(self.__class__, other.__class__) 

143 

144 

145class PrimitiveType(ColumnType): # pylint: disable=too-few-public-methods 

146 """Base class for all Column Primitive Types""" 

147 

148 

149class NumberType(PrimitiveType): # pylint: disable=too-few-public-methods 

150 """Base class for all Column Number Types""" 

151 

152 

153class NullType(PrimitiveType, Singleton): # pylint: disable=too-few-public-methods 

154 """A data type for NULL 

155 

156 Example: 

157 >>> NullType() 

158 NullType() 

159 """ 

160 

161 def __init__(self): 

162 super().__init__("NULL", "NullType()") 

163 

164 

165class FixedType(PrimitiveType): 

166 """A fixed data type. 

167 

168 Example: 

169 >>> FixedType(8) 

170 FixedType(length=8) 

171 >>> FixedType(8)==FixedType(8) 

172 True 

173 """ 

174 

175 _instances: Dict[int, "FixedType"] = {} 

176 

177 def __new__(cls, length: int): 

178 cls._instances[length] = cls._instances.get(length) or object.__new__(cls) 

179 return cls._instances[length] 

180 

181 def __init__(self, length: int): 

182 if not self._initialized: 

183 super().__init__(f"fixed({length})", f"FixedType(length={length})") 

184 self._length = length 

185 

186 @property 

187 def length(self) -> int: # pragma: no cover 

188 """ 

189 The length of the fixed type 

190 """ 

191 return self._length 

192 

193 

194class DecimalType(NumberType): 

195 """A fixed data type. 

196 

197 Example: 

198 >>> DecimalType(32, 3) 

199 DecimalType(precision=32, scale=3) 

200 >>> DecimalType(8, 3)==DecimalType(8, 3) 

201 True 

202 """ 

203 

204 max_precision: ClassVar[int] = 38 

205 max_scale: ClassVar[int] = 38 

206 _instances: Dict[Tuple[int, int], "DecimalType"] = {} 

207 

208 def __new__(cls, precision: int, scale: int): 

209 key = ( 

210 min(precision, DecimalType.max_precision), 

211 min(scale, DecimalType.max_scale), 

212 ) 

213 cls._instances[key] = cls._instances.get(key) or object.__new__(cls) 

214 return cls._instances[key] 

215 

216 def __init__(self, precision: int, scale: int): 

217 

218 if not self._initialized: 

219 super().__init__( 

220 f"decimal({precision}, {scale})", 

221 f"DecimalType(precision={precision}, scale={scale})", 

222 ) 

223 self._precision = min(precision, DecimalType.max_precision) 

224 self._scale = min(scale, DecimalType.max_scale) 

225 

226 @property 

227 def precision(self) -> int: # pragma: no cover 

228 """ 

229 Decimal's precision 

230 """ 

231 return self._precision 

232 

233 @property 

234 def scale(self) -> int: # pragma: no cover 

235 """ 

236 Decimal's scale 

237 """ 

238 return self._scale 

239 

240 

241class NestedField(ColumnType): 

242 """Represents a field of a struct, a map key, a map value, or a list element. 

243 

244 This is where field IDs, names, docs, and nullability are tracked. 

245 """ 

246 

247 _instances: Dict[ 

248 Tuple[bool, str, ColumnType, Optional[str]], 

249 "NestedField", 

250 ] = {} 

251 

252 def __new__( 

253 cls, 

254 name: "ast.Name", 

255 field_type: ColumnType, 

256 is_optional: bool = True, 

257 doc: Optional[str] = None, 

258 ): 

259 if isinstance(name, str): # pragma: no cover 

260 from dj.sql.parsing.ast import ( # pylint: disable=import-outside-toplevel 

261 Name, 

262 ) 

263 

264 name = Name(name) 

265 

266 key = (is_optional, name.name, field_type, doc) 

267 cls._instances[key] = cls._instances.get(key) or object.__new__(cls) 

268 return cls._instances[key] 

269 

270 def __init__( 

271 self, 

272 name: "ast.Name", 

273 field_type: ColumnType, 

274 is_optional: bool = True, 

275 doc: Optional[str] = None, 

276 ): 

277 if not self._initialized: 

278 if isinstance(name, str): # pragma: no cover 

279 from dj.sql.parsing.ast import ( # pylint: disable=import-outside-toplevel 

280 Name, 

281 ) 

282 

283 name = Name(name) 

284 doc_string = "" if doc is None else f", doc={repr(doc)}" 

285 super().__init__( 

286 ( 

287 f"{name}: {field_type}" 

288 f"{' NOT NULL' if not is_optional else ''}" 

289 + ("" if doc is None else f" {doc}") 

290 ), 

291 f"NestedField(name={repr(name)}, " 

292 f"field_type={repr(field_type)}, " 

293 f"is_optional={is_optional}" 

294 f"{doc_string})", 

295 ) 

296 self._is_optional = is_optional 

297 self._name = name 

298 self._type = field_type 

299 self._doc = doc 

300 

301 @property 

302 def is_optional(self) -> bool: 

303 """ 

304 Whether the field is optional 

305 """ 

306 return self._is_optional # pragma: no cover 

307 

308 @property 

309 def is_required(self) -> bool: 

310 """ 

311 Whether the field is required 

312 """ 

313 return not self._is_optional # pragma: no cover 

314 

315 @property 

316 def name(self) -> "ast.Name": 

317 """ 

318 The name of the field 

319 """ 

320 return self._name 

321 

322 @property 

323 def doc(self) -> Optional[str]: 

324 """ 

325 The docstring of the field 

326 """ 

327 return self._doc # pragma: no cover 

328 

329 @property 

330 def type(self) -> ColumnType: 

331 """ 

332 The field's type 

333 """ 

334 return self._type 

335 

336 

337class StructType(ColumnType): 

338 """A struct type 

339 

340 Example: 

341 >>> StructType( \ 

342 NestedField("required_field", StringType(), False, "a required field"), \ 

343 NestedField("optional_field", IntegerType(), True, "an optional field") \ 

344 ) 

345 StructType(NestedField(name=Name(name='required_field', quote_style='', namespace=None), \ 

346field_type=StringType(), is_optional=False, doc='a required field'), \ 

347NestedField(name=Name(name='optional_field', quote_style='', namespace=None), \ 

348field_type=IntegerType(), is_optional=True, doc='an optional field')) 

349 """ 

350 

351 _instances: Dict[Tuple[NestedField, ...], "StructType"] = {} 

352 

353 def __new__(cls, *fields: NestedField): 

354 cls._instances[fields] = cls._instances.get(fields) or object.__new__(cls) 

355 return cls._instances[fields] 

356 

357 def __init__(self, *fields: NestedField): 

358 if not self._initialized: 

359 super().__init__( 

360 f"struct<{', '.join(map(str, fields))}>", 

361 f"StructType{repr(fields)}", 

362 ) 

363 self._fields = fields 

364 

365 @property 

366 def fields(self) -> Tuple[NestedField, ...]: 

367 """ 

368 Returns the struct's fields. 

369 """ 

370 return self._fields # pragma: no cover 

371 

372 

373class ListType(ColumnType): 

374 """A list type 

375 

376 Example: 

377 >>> ListType(element_type=StringType()) 

378 ListType(element_type=StringType()) 

379 """ 

380 

381 _instances: Dict[Tuple[bool, int, ColumnType], "ListType"] = {} 

382 

383 def __new__( 

384 cls, 

385 element_type: ColumnType, 

386 ): 

387 key = (element_type,) 

388 cls._instances[key] = cls._instances.get(key) or object.__new__(cls) # type: ignore 

389 return cls._instances[key] # type: ignore 

390 

391 def __init__( 

392 self, 

393 element_type: ColumnType, 

394 ): 

395 

396 if not self._initialized: 

397 super().__init__( 

398 f"array<{element_type}>", 

399 f"ListType(element_type={repr(element_type)})", 

400 ) 

401 self._element_field = NestedField( 

402 name="col", # type: ignore 

403 field_type=element_type, 

404 is_optional=False, # type: ignore 

405 ) 

406 

407 @property 

408 def element(self) -> NestedField: 

409 """ 

410 Returns the list's element 

411 """ 

412 return self._element_field 

413 

414 

415class MapType(ColumnType): 

416 """A map type""" 

417 

418 _instances: Dict[Tuple[ColumnType, ColumnType], "MapType"] = {} 

419 

420 def __new__( 

421 cls, 

422 key_type: ColumnType, 

423 value_type: ColumnType, 

424 ): 

425 impl_key = (key_type, value_type) 

426 cls._instances[impl_key] = cls._instances.get(impl_key) or object.__new__(cls) 

427 return cls._instances[impl_key] 

428 

429 def __init__( 

430 self, 

431 key_type: ColumnType, 

432 value_type: ColumnType, 

433 ): 

434 

435 if not self._initialized: 

436 super().__init__( 

437 f"map<{key_type}, {value_type}>", 

438 ) 

439 self._key_field = NestedField( 

440 name="key", # type: ignore 

441 field_type=key_type, 

442 is_optional=False, # type: ignore 

443 ) 

444 self._value_field = NestedField( 

445 name="value", # type: ignore 

446 field_type=value_type, 

447 is_optional=False, # type: ignore 

448 ) 

449 

450 @property 

451 def key(self) -> NestedField: 

452 """ 

453 The map's key 

454 """ 

455 return self._key_field 

456 

457 @property 

458 def value(self) -> NestedField: 

459 """ 

460 The map's value 

461 """ 

462 return self._value_field 

463 

464 

465class BooleanType(PrimitiveType, Singleton): 

466 """A boolean data type can be represented using an instance of this class. 

467 

468 Example: 

469 >>> column_foo = BooleanType() 

470 >>> isinstance(column_foo, BooleanType) 

471 True 

472 """ 

473 

474 def __init__(self): 

475 super().__init__("boolean", "BooleanType()") 

476 

477 

478class IntegerBase(NumberType, Singleton): 

479 """Base class for all integer types""" 

480 

481 max: ClassVar[int] 

482 min: ClassVar[int] 

483 

484 def check_bounds(self, value: int) -> bool: 

485 """ 

486 Check whether a value fits within the Integer min and max 

487 """ 

488 return self.__class__.min < value < self.__class__.max 

489 

490 

491class IntegerType(IntegerBase): 

492 """An Integer data type can be represented using an instance of this class. Integers are 

493 32-bit signed and can be promoted to Longs. 

494 

495 Example: 

496 >>> column_foo = IntegerType() 

497 >>> isinstance(column_foo, IntegerType) 

498 True 

499 

500 Attributes: 

501 max (int): The maximum allowed value for Integers, inherited from the 

502 canonical Column implementation 

503 in Java (returns `2147483647`) 

504 min (int): The minimum allowed value for Integers, inherited from the 

505 canonical Column implementation 

506 in Java (returns `-2147483648`) 

507 """ 

508 

509 max: ClassVar[int] = 2147483647 

510 

511 min: ClassVar[int] = -2147483648 

512 

513 def __init__(self): 

514 super().__init__("int", "IntegerType()") 

515 

516 

517class TinyIntType(IntegerBase): 

518 """A TinyInt data type can be represented using an instance of this class. TinyInts are 

519 8-bit signed integers. 

520 

521 Example: 

522 >>> column_foo = TinyIntType() 

523 >>> isinstance(column_foo, TinyIntType) 

524 True 

525 

526 Attributes: 

527 max (int): The maximum allowed value for TinyInts (returns `127`). 

528 min (int): The minimum allowed value for TinyInts (returns `-128`). 

529 """ 

530 

531 max: ClassVar[int] = 127 

532 

533 min: ClassVar[int] = -128 

534 

535 def __init__(self): 

536 super().__init__("tinyint", "TinyIntType()") 

537 

538 

539class SmallIntType(IntegerBase): # pylint: disable=R0901 

540 """A SmallInt data type can be represented using an instance of this class. SmallInts are 

541 16-bit signed integers. 

542 

543 Example: 

544 >>> column_foo = SmallIntType() 

545 >>> isinstance(column_foo, SmallIntType) 

546 True 

547 

548 Attributes: 

549 max (int): The maximum allowed value for SmallInts (returns `32767`). 

550 min (int): The minimum allowed value for SmallInts (returns `-32768`). 

551 """ 

552 

553 max: ClassVar[int] = 32767 

554 

555 min: ClassVar[int] = -32768 

556 

557 def __init__(self): 

558 super().__init__("smallint", "SmallIntType()") 

559 

560 

561class BigIntType(IntegerBase): 

562 """A Long data type can be represented using an instance of this class. Longs are 

563 64-bit signed integers. 

564 

565 Example: 

566 >>> column_foo = BigIntType() 

567 >>> isinstance(column_foo, BigIntType) 

568 True 

569 

570 Attributes: 

571 max (int): The maximum allowed value for Longs, inherited from the 

572 canonical Column implementation 

573 in Java. (returns `9223372036854775807`) 

574 min (int): The minimum allowed value for Longs, inherited from the 

575 canonical Column implementation 

576 in Java (returns `-9223372036854775808`) 

577 """ 

578 

579 max: ClassVar[int] = 9223372036854775807 

580 

581 min: ClassVar[int] = -9223372036854775808 

582 

583 def __init__(self): 

584 super().__init__("bigint", "BigIntType()") 

585 

586 

587class LongType(BigIntType): # pylint: disable=R0901 

588 """A Long data type can be represented using an instance of this class. Longs are 

589 64-bit signed integers. 

590 

591 Example: 

592 >>> column_foo = LongType() 

593 >>> column_foo == LongType() 

594 True 

595 

596 Attributes: 

597 max (int): The maximum allowed value for Longs, inherited from the 

598 canonical Column implementation 

599 in Java. (returns `9223372036854775807`) 

600 min (int): The minimum allowed value for Longs, inherited from the 

601 canonical Column implementation 

602 in Java (returns `-9223372036854775808`) 

603 """ 

604 

605 def __new__(cls, *args, **kwargs): 

606 self = super().__new__(BigIntType, *args, **kwargs) 

607 super(BigIntType, self).__init__("long", "LongType()") 

608 return self 

609 

610 

611class FloatingBase(NumberType, Singleton): 

612 """Base class for all floating types""" 

613 

614 

615class FloatType(FloatingBase): 

616 """A Float data type can be represented using an instance of this class. Floats are 

617 32-bit IEEE 754 floating points and can be promoted to Doubles. 

618 

619 Example: 

620 >>> column_foo = FloatType() 

621 >>> isinstance(column_foo, FloatType) 

622 True 

623 """ 

624 

625 def __init__(self): 

626 super().__init__("float", "FloatType()") 

627 

628 

629class DoubleType(FloatingBase): 

630 """A Double data type can be represented using an instance of this class. Doubles are 

631 64-bit IEEE 754 floating points. 

632 

633 Example: 

634 >>> column_foo = DoubleType() 

635 >>> isinstance(column_foo, DoubleType) 

636 True 

637 """ 

638 

639 def __init__(self): 

640 super().__init__("double", "DoubleType()") 

641 

642 

643class DateTimeBase(PrimitiveType, Singleton): 

644 """ 

645 Base class for date and time types. 

646 """ 

647 

648 # pylint: disable=invalid-name 

649 class Unit(str, Enum): 

650 """ 

651 Units used for date and time functions and intervals 

652 """ 

653 

654 dayofyear = "DAYOFYEAR" 

655 year = "YEAR" 

656 day = "DAY" 

657 microsecond = "MICROSECOND" 

658 month = "MONTH" 

659 week = "WEEK" 

660 minute = "MINUTE" 

661 second = "SECOND" 

662 quarter = "QUARTER" 

663 hour = "HOUR" 

664 millisecond = "MILLISECOND" 

665 

666 # pylint: enable=invalid-name 

667 

668 

669class DateType(DateTimeBase): 

670 """A Date data type can be represented using an instance of this class. Dates are 

671 calendar dates without a timezone or time. 

672 

673 Example: 

674 >>> column_foo = DateType() 

675 >>> isinstance(column_foo, DateType) 

676 True 

677 """ 

678 

679 def __init__(self): 

680 super().__init__("date", "DateType()") 

681 

682 

683class TimeType(DateTimeBase): 

684 """A Time data type can be represented using an instance of this class. Times 

685 have microsecond precision and are a time of day without a date or timezone. 

686 

687 Example: 

688 >>> column_foo = TimeType() 

689 >>> isinstance(column_foo, TimeType) 

690 True 

691 """ 

692 

693 def __init__(self): 

694 super().__init__("time", "TimeType()") 

695 

696 

697class TimestampType(PrimitiveType, Singleton): 

698 """A Timestamp data type can be represented using an instance of this class. Timestamps in 

699 Column have microsecond precision and include a date and a time of day without a timezone. 

700 

701 Example: 

702 >>> column_foo = TimestampType() 

703 >>> isinstance(column_foo, TimestampType) 

704 True 

705 """ 

706 

707 def __init__(self): 

708 super().__init__("timestamp", "TimestampType()") 

709 

710 

711class TimestamptzType(PrimitiveType, Singleton): 

712 """A Timestamptz data type can be represented using an instance of this class. Timestamptzs in 

713 Column are stored as UTC and include a date and a time of day with a timezone. 

714 

715 Example: 

716 >>> column_foo = TimestamptzType() 

717 >>> isinstance(column_foo, TimestamptzType) 

718 True 

719 """ 

720 

721 def __init__(self): 

722 super().__init__("timestamptz", "TimestamptzType()") 

723 

724 

725class IntervalTypeBase(PrimitiveType): 

726 """A base class for all interval types""" 

727 

728 

729class DayTimeIntervalType(IntervalTypeBase): 

730 """A DayTimeIntervalType type. 

731 

732 Example: 

733 >>> DayTimeIntervalType()==DayTimeIntervalType("DAY", "SECOND") 

734 True 

735 """ 

736 

737 _instances: Dict[Tuple[str, str], "DayTimeIntervalType"] = {} 

738 

739 def __new__( 

740 cls, 

741 from_: DateTimeBase.Unit = DateTimeBase.Unit.day, 

742 to_: Optional[DateTimeBase.Unit] = DateTimeBase.Unit.second, 

743 ): 

744 key = (from_.upper(), to_.upper()) # type: ignore 

745 cls._instances[key] = cls._instances.get(key) or object.__new__(cls) 

746 return cls._instances[key] 

747 

748 def __init__( 

749 self, 

750 from_: DateTimeBase.Unit = DateTimeBase.Unit.day, 

751 to_: Optional[DateTimeBase.Unit] = DateTimeBase.Unit.second, 

752 ): 

753 if not self._initialized: 

754 from_ = from_.upper() # type: ignore 

755 to_ = to_.upper() # type: ignore 

756 to_str = f" TO {to_}" if to_ else "" 

757 to_repr = f', to="{to_}"' if to_ else "" 

758 super().__init__( 

759 f"INTERVAL {from_}{to_str}", 

760 f'DayTimeIntervalType(from="{from_}"{to_repr})', 

761 ) 

762 self._from = from_ 

763 self._to = to_ 

764 

765 @property 

766 def from_(self) -> str: # pylint: disable=missing-function-docstring 

767 return self._from # pragma: no cover 

768 

769 @property 

770 def to_( # pylint: disable=missing-function-docstring 

771 self, 

772 ) -> Optional[str]: 

773 return self._to # pragma: no cover 

774 

775 

776class YearMonthIntervalType(IntervalTypeBase): 

777 """A YearMonthIntervalType type. 

778 

779 Example: 

780 >>> YearMonthIntervalType()==YearMonthIntervalType("YEAR", "MONTH") 

781 True 

782 """ 

783 

784 _instances: Dict[Tuple[str, str], "YearMonthIntervalType"] = {} 

785 

786 def __new__( 

787 cls, 

788 from_: DateTimeBase.Unit = DateTimeBase.Unit.year, 

789 to_: Optional[DateTimeBase.Unit] = DateTimeBase.Unit.month, 

790 ): 

791 key = (from_.upper(), to_.upper()) # type: ignore 

792 cls._instances[key] = cls._instances.get(key) or object.__new__(cls) 

793 return cls._instances[key] 

794 

795 def __init__( 

796 self, 

797 from_: DateTimeBase.Unit = DateTimeBase.Unit.year, 

798 to_: Optional[DateTimeBase.Unit] = DateTimeBase.Unit.month, 

799 ): 

800 if not self._initialized: 

801 from_ = from_.upper() # type: ignore 

802 to_ = to_.upper() # type: ignore 

803 to_str = f" TO {to_}" if to_ else "" 

804 to_repr = f', to="{to_}"' if to_ else "" 

805 super().__init__( 

806 f"INTERVAL {from_}{to_str}", 

807 f'YearMonthIntervalType(from="{from_}"{to_repr})', 

808 ) 

809 self._from = from_ 

810 self._to = to_ 

811 

812 @property 

813 def from_(self) -> str: # pylint: disable=missing-function-docstring 

814 return self._from # pragma: no cover 

815 

816 @property 

817 def to_( # pylint: disable=missing-function-docstring 

818 self, 

819 ) -> Optional[str]: 

820 return self._to # pragma: no cover 

821 

822 

823class StringBase(PrimitiveType, Singleton): 

824 """Base class for all string types""" 

825 

826 

827class StringType(StringBase): 

828 """A String data type can be represented using an instance of this class. Strings in 

829 Column are arbitrary-length character sequences and are encoded with UTF-8. 

830 

831 Example: 

832 >>> column_foo = StringType() 

833 >>> isinstance(column_foo, StringType) 

834 True 

835 """ 

836 

837 def __init__(self): 

838 super().__init__("string", "StringType()") 

839 

840 

841class VarcharType(StringBase): 

842 """A VarcharType data type can be represented using an instance of this class. 

843 Varchars in Column are arbitrary-length character sequences and are 

844 encoded with UTF-8. 

845 

846 Example: 

847 >>> column_foo = VarcharType() 

848 >>> isinstance(column_foo, VarcharType) 

849 True 

850 """ 

851 

852 def __init__(self): 

853 super().__init__("varchar", "VarcharType()") 

854 

855 

856class UUIDType(PrimitiveType, Singleton): 

857 """A UUID data type can be represented using an instance of this class. UUIDs in 

858 Column are universally unique identifiers. 

859 

860 Example: 

861 >>> column_foo = UUIDType() 

862 >>> isinstance(column_foo, UUIDType) 

863 True 

864 """ 

865 

866 def __init__(self): 

867 super().__init__("uuid", "UUIDType()") 

868 

869 

870class BinaryType(PrimitiveType, Singleton): 

871 """A Binary data type can be represented using an instance of this class. Binarys in 

872 Column are arbitrary-length byte arrays. 

873 

874 Example: 

875 >>> column_foo = BinaryType() 

876 >>> isinstance(column_foo, BinaryType) 

877 True 

878 """ 

879 

880 def __init__(self): 

881 super().__init__("binary", "BinaryType()") 

882 

883 

884class WildcardType(PrimitiveType, Singleton): 

885 """A Wildcard datatype. 

886 

887 Example: 

888 >>> column_foo = WildcardType() 

889 >>> isinstance(column_foo, WildcardType) 

890 True 

891 """ 

892 

893 def __init__(self): 

894 super().__init__("wildcard", "WildcardType()") 

895 

896 

897# Define the primitive data types and their corresponding Python classes 

898PRIMITIVE_TYPES: Dict[str, PrimitiveType] = { 

899 "bool": BooleanType(), 

900 "boolean": BooleanType(), 

901 "varchar": VarcharType(), 

902 "bigint": BigIntType(), 

903 "int": IntegerType(), 

904 "long": BigIntType(), 

905 "float": FloatType(), 

906 "double": DoubleType(), 

907 "date": DateType(), 

908 "time": TimeType(), 

909 "timestamp": TimestampType(), 

910 "timestamptz": TimestamptzType(), 

911 "string": StringType(), 

912 "uuid": UUIDType(), 

913 "byte": BinaryType(), 

914 "binary": BinaryType(), 

915 "none": NullType(), 

916 "null": NullType(), 

917}