ormlambda 4.0.5__tar.gz → 4.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. {ormlambda-4.0.5 → ormlambda-4.4.0}/PKG-INFO +2 -3
  2. {ormlambda-4.0.5 → ormlambda-4.4.0}/pyproject.toml +1 -1
  3. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/__init__.py +2 -6
  4. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/common/enums/__init__.py +2 -1
  5. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/common/enums/condition_types.py +4 -1
  6. ormlambda-4.4.0/src/ormlambda/common/enums/union_type.py +9 -0
  7. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/common/errors/__init__.py +9 -9
  8. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/common/interfaces/__init__.py +0 -1
  9. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/dialects/mysql/base.py +291 -37
  10. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/dialects/mysql/caster/types/iterable.py +5 -1
  11. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/dialects/mysql/clauses/ST_AsText.py +2 -2
  12. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/dialects/mysql/clauses/ST_Contains.py +2 -2
  13. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/errors.py +2 -2
  14. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/repository/response.py +2 -1
  15. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/sql/clause_info/__init__.py +0 -1
  16. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/sql/clause_info/clause_info.py +15 -3
  17. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/sql/clause_info/interface/__init__.py +0 -1
  18. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/sql/clauses/__init__.py +0 -1
  19. ormlambda-4.4.0/src/ormlambda/sql/clauses/alias.py +52 -0
  20. ormlambda-4.4.0/src/ormlambda/sql/clauses/delete.py +21 -0
  21. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/sql/clauses/group_by.py +2 -2
  22. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/sql/clauses/having.py +2 -2
  23. ormlambda-4.4.0/src/ormlambda/sql/clauses/insert.py +25 -0
  24. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/sql/clauses/joins.py +1 -1
  25. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/sql/clauses/order.py +2 -2
  26. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/sql/clauses/select.py +7 -5
  27. ormlambda-4.4.0/src/ormlambda/sql/clauses/update.py +23 -0
  28. ormlambda-4.4.0/src/ormlambda/sql/clauses/upsert.py +23 -0
  29. ormlambda-4.4.0/src/ormlambda/sql/clauses/where.py +42 -0
  30. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/sql/column/column.py +1 -1
  31. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/sql/column/column_proxy.py +28 -0
  32. ormlambda-4.4.0/src/ormlambda/sql/comparer.py +192 -0
  33. ormlambda-4.4.0/src/ormlambda/sql/functions/__init__.py +87 -0
  34. ormlambda-4.4.0/src/ormlambda/sql/functions/aggregate/avg.py +15 -0
  35. {ormlambda-4.0.5/src/ormlambda/sql/functions → ormlambda-4.4.0/src/ormlambda/sql/functions/aggregate}/concat.py +2 -2
  36. {ormlambda-4.0.5/src/ormlambda/sql/clauses → ormlambda-4.4.0/src/ormlambda/sql/functions/aggregate}/count.py +2 -2
  37. ormlambda-4.4.0/src/ormlambda/sql/functions/aggregate/max.py +15 -0
  38. ormlambda-4.4.0/src/ormlambda/sql/functions/aggregate/min.py +15 -0
  39. ormlambda-4.4.0/src/ormlambda/sql/functions/aggregate/sum.py +15 -0
  40. ormlambda-4.4.0/src/ormlambda/sql/functions/base.py +26 -0
  41. ormlambda-4.4.0/src/ormlambda/sql/functions/datetime/__init__.py +0 -0
  42. ormlambda-4.0.5/src/ormlambda/sql/clause_info/interface/IAggregate.py → ormlambda-4.4.0/src/ormlambda/sql/functions/interface/__init__.py +8 -4
  43. ormlambda-4.4.0/src/ormlambda/sql/functions/mathematical/__init__.py +0 -0
  44. ormlambda-4.4.0/src/ormlambda/sql/functions/mathematical/abs.py +15 -0
  45. ormlambda-4.4.0/src/ormlambda/sql/functions/mathematical/ceil.py +15 -0
  46. ormlambda-4.4.0/src/ormlambda/sql/functions/mathematical/floor.py +15 -0
  47. ormlambda-4.4.0/src/ormlambda/sql/functions/mathematical/mod.py +16 -0
  48. ormlambda-4.4.0/src/ormlambda/sql/functions/mathematical/pow.py +16 -0
  49. ormlambda-4.4.0/src/ormlambda/sql/functions/mathematical/rand.py +15 -0
  50. ormlambda-4.4.0/src/ormlambda/sql/functions/mathematical/round.py +15 -0
  51. ormlambda-4.4.0/src/ormlambda/sql/functions/mathematical/sqrt.py +15 -0
  52. ormlambda-4.4.0/src/ormlambda/sql/functions/mathematical/truncate.py +17 -0
  53. ormlambda-4.4.0/src/ormlambda/sql/functions/string/__init__.py +0 -0
  54. ormlambda-4.4.0/src/ormlambda/sql/types.py +44 -0
  55. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/statements/base_statement.py +8 -5
  56. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/statements/interfaces/IStatements.py +17 -13
  57. ormlambda-4.4.0/src/ormlambda/statements/query_builder.py +259 -0
  58. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/statements/statements.py +55 -41
  59. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/util/preloaded.py +2 -0
  60. ormlambda-4.0.5/src/ormlambda/common/abstract_classes/__init__.py +0 -1
  61. ormlambda-4.0.5/src/ormlambda/common/abstract_classes/non_query_base.py +0 -37
  62. ormlambda-4.0.5/src/ormlambda/common/interfaces/INonQueryCommand.py +0 -9
  63. ormlambda-4.0.5/src/ormlambda/sql/clauses/alias.py +0 -14
  64. ormlambda-4.0.5/src/ormlambda/sql/clauses/delete.py +0 -71
  65. ormlambda-4.0.5/src/ormlambda/sql/clauses/insert.py +0 -104
  66. ormlambda-4.0.5/src/ormlambda/sql/clauses/interfaces/IDelete.py +0 -6
  67. ormlambda-4.0.5/src/ormlambda/sql/clauses/interfaces/IInsert.py +0 -6
  68. ormlambda-4.0.5/src/ormlambda/sql/clauses/interfaces/IUpdate.py +0 -7
  69. ormlambda-4.0.5/src/ormlambda/sql/clauses/interfaces/IUpsert.py +0 -6
  70. ormlambda-4.0.5/src/ormlambda/sql/clauses/interfaces/__init__.py +0 -4
  71. ormlambda-4.0.5/src/ormlambda/sql/clauses/update.py +0 -87
  72. ormlambda-4.0.5/src/ormlambda/sql/clauses/upsert.py +0 -77
  73. ormlambda-4.0.5/src/ormlambda/sql/clauses/where.py +0 -46
  74. ormlambda-4.0.5/src/ormlambda/sql/comparer.py +0 -136
  75. ormlambda-4.0.5/src/ormlambda/sql/functions/__init__.py +0 -4
  76. ormlambda-4.0.5/src/ormlambda/sql/functions/max.py +0 -24
  77. ormlambda-4.0.5/src/ormlambda/sql/functions/min.py +0 -24
  78. ormlambda-4.0.5/src/ormlambda/sql/functions/sum.py +0 -25
  79. ormlambda-4.0.5/src/ormlambda/sql/types.py +0 -30
  80. ormlambda-4.0.5/src/ormlambda/statements/query_builder.py +0 -331
  81. {ormlambda-4.0.5 → ormlambda-4.4.0}/AUTHORS +0 -0
  82. {ormlambda-4.0.5 → ormlambda-4.4.0}/LICENSE +0 -0
  83. {ormlambda-4.0.5 → ormlambda-4.4.0}/README.md +0 -0
  84. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/caster/__init__.py +0 -0
  85. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/caster/base_caster.py +0 -0
  86. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/caster/caster.py +0 -0
  87. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/caster/interfaces/ICaster.py +0 -0
  88. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/caster/interfaces/__init__.py +0 -0
  89. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/common/__init__.py +0 -0
  90. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/common/enums/join_type.py +0 -0
  91. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/common/enums/order_type.py +0 -0
  92. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/common/global_checker.py +0 -0
  93. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/common/interfaces/IJoinSelector.py +0 -0
  94. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/common/interfaces/IQueryCommand.py +0 -0
  95. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/dialects/__init__.py +0 -0
  96. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/dialects/default/__init__.py +0 -0
  97. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/dialects/default/base.py +0 -0
  98. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/dialects/mysql/__init__.py +0 -0
  99. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/dialects/mysql/caster/__init__.py +0 -0
  100. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/dialects/mysql/caster/caster.py +0 -0
  101. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/dialects/mysql/caster/types/__init__.py +0 -0
  102. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/dialects/mysql/caster/types/boolean.py +0 -0
  103. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/dialects/mysql/caster/types/bytes.py +0 -0
  104. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/dialects/mysql/caster/types/date.py +0 -0
  105. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/dialects/mysql/caster/types/datetime.py +0 -0
  106. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/dialects/mysql/caster/types/decimal.py +0 -0
  107. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/dialects/mysql/caster/types/float.py +0 -0
  108. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/dialects/mysql/caster/types/int.py +0 -0
  109. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/dialects/mysql/caster/types/json.py +0 -0
  110. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/dialects/mysql/caster/types/none.py +0 -0
  111. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/dialects/mysql/caster/types/point.py +0 -0
  112. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/dialects/mysql/caster/types/string.py +0 -0
  113. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/dialects/mysql/clauses/__init__.py +0 -0
  114. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/dialects/mysql/mysqlconnector.py +0 -0
  115. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/dialects/mysql/repository/__init__.py +0 -0
  116. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/dialects/mysql/repository/pool_types.py +0 -0
  117. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/dialects/mysql/repository/repository.py +0 -0
  118. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/dialects/mysql/types.py +0 -0
  119. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/dialects/sqlite/__init__.py +0 -0
  120. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/dialects/sqlite/base.py +0 -0
  121. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/dialects/sqlite/pysqlite.py +0 -0
  122. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/engine/__init__.py +0 -0
  123. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/engine/base.py +0 -0
  124. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/engine/create.py +0 -0
  125. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/engine/url.py +0 -0
  126. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/engine/utils.py +0 -0
  127. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/env.py +0 -0
  128. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/model/__init__.py +0 -0
  129. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/model/base_model.py +0 -0
  130. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/repository/__init__.py +0 -0
  131. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/repository/base_repository.py +0 -0
  132. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/repository/interfaces/IDatabaseConnection.py +0 -0
  133. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/repository/interfaces/IRepositoryBase.py +0 -0
  134. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/repository/interfaces/__init__.py +0 -0
  135. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/sql/__init__.py +0 -0
  136. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/sql/clause_info/interface/IClauseInfo.py +0 -0
  137. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/sql/clauses/join/__init__.py +0 -0
  138. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/sql/clauses/join/join_context.py +0 -0
  139. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/sql/clauses/limit.py +0 -0
  140. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/sql/clauses/offset.py +0 -0
  141. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/sql/column/__init__.py +0 -0
  142. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/sql/column_table_proxy.py +0 -0
  143. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/sql/compiler.py +0 -0
  144. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/sql/context/__init__.py +0 -0
  145. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/sql/ddl.py +0 -0
  146. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/sql/elements.py +0 -0
  147. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/sql/foreign_key.py +0 -0
  148. {ormlambda-4.0.5/src/ormlambda/sql/interfaces → ormlambda-4.4.0/src/ormlambda/sql/functions/aggregate}/__init__.py +0 -0
  149. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/sql/sqltypes.py +0 -0
  150. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/sql/table/__init__.py +0 -0
  151. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/sql/table/fields.py +0 -0
  152. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/sql/table/table.py +0 -0
  153. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/sql/table/table_constructor.py +0 -0
  154. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/sql/table/table_proxy.py +0 -0
  155. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/sql/type_api.py +0 -0
  156. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/sql/visitors.py +0 -0
  157. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/statements/__init__.py +0 -0
  158. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/statements/interfaces/__init__.py +0 -0
  159. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/statements/types.py +0 -0
  160. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/types/__init__.py +0 -0
  161. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/types/metadata.py +0 -0
  162. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/util/__init__.py +0 -0
  163. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/util/langhelpers.py +0 -0
  164. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/util/module_tree/__init__.py +0 -0
  165. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/util/module_tree/dfs_traversal.py +0 -0
  166. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/util/module_tree/dynamic_module.py +0 -0
  167. {ormlambda-4.0.5 → ormlambda-4.4.0}/src/ormlambda/util/typing.py +0 -0
@@ -1,13 +1,12 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.1
2
2
  Name: ormlambda
3
- Version: 4.0.5
3
+ Version: 4.4.0
4
4
  Summary: ORM designed to interact with the database (currently with MySQL) using lambda functions and nested functions
5
5
  Author: p-hzamora
6
6
  Author-email: p.hzamora@icloud.com
7
7
  Requires-Python: >=3.12,<4.0
8
8
  Classifier: Programming Language :: Python :: 3
9
9
  Classifier: Programming Language :: Python :: 3.12
10
- Classifier: Programming Language :: Python :: 3.13
11
10
  Requires-Dist: mysql-connector-python (>=9.0.0,<10.0.0)
12
11
  Requires-Dist: shapely (>=2.0.6,<3.0.0)
13
12
  Description-Content-Type: text/markdown
@@ -3,7 +3,7 @@ line-length = 320
3
3
 
4
4
  [tool.poetry]
5
5
  name = "ormlambda"
6
- version = "4.0.5"
6
+ version = "4.4.0"
7
7
  description = "ORM designed to interact with the database (currently with MySQL) using lambda functions and nested functions"
8
8
  authors = ["p-hzamora <p.hzamora@icloud.com>"]
9
9
  readme = "README.md"
@@ -62,7 +62,6 @@ from .sql.sqltypes import POINT as POINT
62
62
 
63
63
 
64
64
  from .sql.clauses import Alias as Alias
65
- from .sql.clauses import Count as Count
66
65
  from .sql.clauses import Delete as Delete
67
66
  from .sql.clauses import GroupBy as GroupBy
68
67
  from .sql.clauses import Insert as Insert
@@ -76,12 +75,9 @@ from .sql.clauses import Having as Having
76
75
  from .sql.clauses import Update as Update
77
76
  from .sql.clauses import Upsert as Upsert
78
77
 
79
- from .sql.functions import Max as Max
80
- from .sql.functions import Min as Min
81
- from .sql.functions import Concat as Concat
82
- from .sql.functions import Sum as Sum
83
-
78
+ from .sql import functions as functions
84
79
 
80
+ from .sql.functions import * # noqa: F403
85
81
  from . import util as _util
86
82
 
87
83
  _util.import_prefix("ormlambda")
@@ -1,3 +1,4 @@
1
1
  from .join_type import JoinType # noqa: F401
2
2
  from .condition_types import ConditionType # noqa: F401
3
- from .order_type import OrderType # noqa: F401
3
+ from .order_type import OrderType # noqa: F401
4
+ from .union_type import UnionEnum # noqa: F401
@@ -1,7 +1,7 @@
1
1
  from enum import Enum
2
2
 
3
3
 
4
- class ConditionType(Enum):
4
+ class ConditionType(str, Enum):
5
5
  EQUAL = "="
6
6
  LESS_THAN = "<"
7
7
  GREATER_THAN = ">"
@@ -15,3 +15,6 @@ class ConditionType(Enum):
15
15
  NOT_IN = "NOT IN"
16
16
  IS = "IS"
17
17
  IS_NOT = "IS NOT"
18
+
19
+ def __str__(self):
20
+ return super().__str__()
@@ -0,0 +1,9 @@
1
+ import enum
2
+
3
+
4
+ class UnionEnum(str, enum.Enum):
5
+ AND = "AND"
6
+ OR = "OR"
7
+
8
+ def __str__(self):
9
+ return super().__str__()
@@ -18,16 +18,16 @@ class UnmatchedLambdaParameterError(Exception):
18
18
  return f"Unmatched number of parameters in lambda function with the number of tables: Expected {self.expected_params} parameters but found {str(self.found_param)}."
19
19
 
20
20
 
21
- class NotKeysInIAggregateError(Exception):
21
+ class NotKeysInIFunctionError(Exception):
22
22
  def __init__(self, match_regex: list[str], *args: object) -> None:
23
23
  super().__init__(*args)
24
24
  self._match_regex: list[str] = match_regex
25
25
 
26
26
  def __str__(self) -> str:
27
- return f"We cannot use placeholders in IAggregate class. You used {self._match_regex}"
27
+ return f"We cannot use placeholders in IFunction class. You used {self._match_regex}"
28
28
 
29
29
 
30
- class AggregateFunctionError[T](Exception):
30
+ class FunctionFunctionError[T](Exception):
31
31
  def __init__(self, clause: ClauseInfo[T], *args):
32
32
  self.clause = clause
33
33
  super().__init__(*args)
@@ -36,18 +36,18 @@ class AggregateFunctionError[T](Exception):
36
36
  agg_methods = self.__get_all_aggregate_method(self.clause)
37
37
  return f"You cannot use aggregation method like '{agg_methods}' to return model objects. Try specifying 'flavour' attribute as 'dict'."
38
38
 
39
- @util.preload_module("ormlambda.sql.clause_info")
39
+ @util.preload_module("ormlambda.sql.functions")
40
40
  def __get_all_aggregate_method(self, clauses: list[ClauseInfo]) -> str:
41
41
  """
42
- Get the class name of those classes that inherit from 'IAggregate' class in order to create a better error message.
42
+ Get the class name of those classes that inherit from 'IFunction' class in order to create a better error message.
43
43
  """
44
44
 
45
- IAggregate = util.preloaded.sql_clause_info.IAggregate
45
+ IFunction = util.preloaded.sql_functions.IFunction
46
46
  res: set[str] = set()
47
47
  if not isinstance(clauses, tp.Iterable):
48
48
  return clauses.__class__.__name__
49
49
  for clause in clauses:
50
- if isinstance(clause, IAggregate):
50
+ if isinstance(clause, IFunction):
51
51
  res.add(clause.__class__.__name__)
52
52
  return ", ".join(res)
53
53
 
@@ -56,5 +56,5 @@ class NotCallableError(ValueError):
56
56
  def __init__(self, *args):
57
57
  super().__init__(*args)
58
58
 
59
- def __str__(self)->str:
60
- return f"You must provide a function or callable to proceed with the query creation. Passed '{self.args[0].__class__.__name__}' "
59
+ def __str__(self) -> str:
60
+ return f"You must provide a function or callable to proceed with the query creation. Passed '{self.args[0].__class__.__name__}' "
@@ -1,3 +1,2 @@
1
1
  from .IJoinSelector import IJoinSelector as IJoinSelector
2
- from .INonQueryCommand import INonQueryCommand as INonQueryCommand
3
2
  from .IQueryCommand import IQuery as IQuery
@@ -1,19 +1,21 @@
1
1
  from __future__ import annotations
2
2
  from types import ModuleType
3
- from ormlambda import ColumnProxy, ForeignKey, TableProxy
3
+ from ormlambda import ColumnProxy, ForeignKey, TableProxy, Table, Column
4
4
  from ormlambda.sql import compiler
5
5
  from ormlambda.sql.clause_info import ClauseInfo
6
- from ormlambda.common.errors import NotKeysInIAggregateError
6
+ from ormlambda.common.errors import NotKeysInIFunctionError
7
7
  from ormlambda.sql.types import ASTERISK
8
8
 
9
9
 
10
10
  from .. import default
11
- from typing import TYPE_CHECKING, Any, Iterable
11
+ from typing import TYPE_CHECKING, Any, Iterable, Type
12
+ from ormlambda.sql.comparer import Comparer, ComparerCluster
12
13
 
13
14
  if TYPE_CHECKING:
15
+ from ormlambda.sql.functions.interface import IFunction
16
+ from ormlambda.sql.types import ColumnType
14
17
  from test.test_clause_info import ST_Contains
15
18
  from ormlambda import JoinSelector
16
- from ormlambda.sql.comparer import Comparer
17
19
  from ormlambda.sql.column.column import Column
18
20
  from mysql import connector
19
21
  from ormlambda.dialects.mysql.clauses import ST_AsText
@@ -70,7 +72,6 @@ if TYPE_CHECKING:
70
72
  Update,
71
73
  Limit,
72
74
  Offset,
73
- Count,
74
75
  Where,
75
76
  Having,
76
77
  Order,
@@ -78,10 +79,21 @@ if TYPE_CHECKING:
78
79
  )
79
80
 
80
81
  from ormlambda.sql.functions import (
81
- Concat,
82
82
  Max,
83
83
  Min,
84
+ Concat,
84
85
  Sum,
86
+ Count,
87
+ Avg,
88
+ Abs,
89
+ Ceil,
90
+ Floor,
91
+ Round,
92
+ Pow,
93
+ Sqrt,
94
+ Mod,
95
+ Rand,
96
+ Truncate,
85
97
  )
86
98
 
87
99
 
@@ -129,6 +141,12 @@ class MySQLCompiler(compiler.SQLCompiler):
129
141
  column.alias = clause_info.alias_clause
130
142
  return clause_info.query(self.dialect)
131
143
 
144
+ def visit_comparer_cluster(self, cluster: ComparerCluster, **kw) -> str:
145
+ c1 = cluster.left_comparer.compile(self.dialect, **kw).string
146
+ c2 = cluster.right_comparer.compile(self.dialect, **kw).string
147
+
148
+ return f"{c1} {cluster.join} {c2}"
149
+
132
150
  def visit_comparer(self, comparer: Comparer, **kwargs) -> str:
133
151
  from ormlambda.sql.comparer import CleanValue, Comparer
134
152
 
@@ -149,6 +167,48 @@ class MySQLCompiler(compiler.SQLCompiler):
149
167
 
150
168
  return f"{lcond} {comparer.compare} {rcond}"
151
169
 
170
+ def visit_where(self, where: Where) -> str:
171
+ assert (n := len(where.comparers)) == len(where.restrictive)
172
+
173
+ if not where.comparers:
174
+ return ""
175
+
176
+ cond = []
177
+
178
+ for i in range(n):
179
+ comp = where.comparers[i]
180
+
181
+ string = comp.compile(self.dialect).string
182
+
183
+ condition = f"({string})" if isinstance(comp, ComparerCluster) else string
184
+
185
+ union = f" {where.restrictive[i + 1]} " if i != n - 1 else ""
186
+
187
+ condition += union
188
+ cond.append(condition)
189
+ return f" WHERE {"".join(cond)}"
190
+
191
+ def visit_having(self, having: Having) -> str:
192
+ assert (n := len(having.comparers)) == len(having.restrictive)
193
+
194
+ if not having.comparers:
195
+ return ""
196
+
197
+ cond = []
198
+
199
+ for i in range(n):
200
+ comp = having.comparers[i]
201
+
202
+ string = comp.compile(self.dialect).string
203
+
204
+ condition = f"({string})" if isinstance(comp, ComparerCluster) else string
205
+
206
+ union = f" {having.restrictive[i + 1]} " if i != n - 1 else ""
207
+
208
+ condition += union
209
+ cond.append(condition)
210
+ return f" HAVING {"".join(cond)}"
211
+
152
212
  def visit_join(self, join: JoinSelector) -> str:
153
213
  rt = join.rcon.table
154
214
  rtable = TableProxy(table_class=rt, path=join.rcon.path)
@@ -193,18 +253,191 @@ class MySQLCompiler(compiler.SQLCompiler):
193
253
  def visit_limit(self, limit: Limit, **kw):
194
254
  return f"LIMIT {limit._number}"
195
255
 
196
- # TODOH []: include the rest of visit methods
197
- def visit_insert(self, insert: Insert, **kw) -> str:
198
- pass
256
+ # TODOH [x]: include the rest of visit methods
257
+ def visit_insert[T, TProp](self, insert: Insert) -> str:
258
+ def __is_valid[TProp](column: Column[TProp], value: TProp) -> bool:
259
+ """
260
+ We want to delete the column from table when it's specified with an 'AUTO_INCREMENT' or 'AUTO GENERATED ALWAYS AS (__) STORED' statement.
261
+
262
+ if the column is auto-generated, it means the database creates the value for that column, so we must deleted it.
263
+ if the column is primary key and auto-increment, we should be able to create an object with specific pk value.
264
+
265
+ RETURN
266
+ -----
267
+
268
+ - True -> Do not delete the column from dict query
269
+ - False -> Delete the column from dict query
270
+ """
271
+
272
+ is_pk_none_and_auto_increment: bool = all([value is None, column.is_primary_key, column.is_auto_increment])
273
+
274
+ if is_pk_none_and_auto_increment or column.is_auto_generated:
275
+ return False
276
+ return True
277
+
278
+ def __fill_dict_list(list_dict: list[str, TProp], values: list[T]) -> list[Column]:
279
+ if isinstance(values, Iterable):
280
+ for x in values:
281
+ __fill_dict_list(list_dict, x)
282
+
283
+ elif issubclass(values.__class__, Table):
284
+ new_list = []
285
+ for prop in type(values).__dict__.values():
286
+ if not isinstance(prop, Column):
287
+ continue
288
+
289
+ value = getattr(values, prop.column_name)
290
+ if __is_valid(prop, value):
291
+ new_list.append(prop)
292
+ list_dict.append(new_list)
293
+
294
+ else:
295
+ raise Exception(f"Tipo de dato'{type(values)}' no esperado")
296
+ return None
297
+
298
+ if not isinstance(insert.values, Iterable):
299
+ values = (insert.values,)
300
+ else:
301
+ values = insert.values
302
+ valid_cols: list[list[Column]] = []
303
+ __fill_dict_list(valid_cols, values)
304
+
305
+ col_names: list[str] = []
306
+ wildcards: list[str] = []
307
+ col_values: list[list[str]] = []
308
+ for i, cols in enumerate(valid_cols):
309
+ col_values.append([])
310
+ CASTER = self.dialect.caster()
311
+ for col in cols:
312
+ clean_data = CASTER.for_column(col, insert.values[i]) # .resolve(insert.values[i][col])
313
+ if i == 0:
314
+ col_names.append(col.column_name)
315
+ wildcards.append(clean_data.wildcard_to_insert())
316
+ # COMMENT: avoid MySQLWriteCastBase.resolve when using PLACEHOLDERs
317
+ col_values[-1].append(clean_data.to_database)
318
+
319
+ join_cols = ", ".join(col_names)
320
+ unknown_rows = f"({', '.join(wildcards)})" # The number of "%s" must match the dict 'dicc_0' length
321
+
322
+ insert.cleaned_values = [tuple(x) for x in col_values]
323
+ query = f"INSERT INTO {insert.table.__table_name__} {f'({join_cols})'} VALUES {unknown_rows}"
324
+ return query
199
325
 
200
326
  def visit_delete(self, delete: Delete, **kw) -> str:
201
- pass
327
+ return f"DELETE FROM {delete.table.__table_name__}"
202
328
 
203
329
  def visit_upsert(self, upsert: Upsert, **kw) -> str:
204
- pass
330
+ """
331
+ Generate MySQL UPSERT query using INSERT ... ON DUPLICATE KEY UPDATE.
332
+
333
+ Works with both single dictionaries and lists of changes. The function only
334
+ generates placeholders and column names - actual values are bound separately.
335
+
336
+ Parameters
337
+ ----------
338
+ upsert : Upsert
339
+ Upsert object containing table and values information
340
+
341
+ Returns
342
+ -------
343
+ str
344
+ SQL query string with placeholders
345
+
346
+ Examples
347
+ --------
348
+ Generated SQL:
349
+ INSERT INTO users (id, name, email)
350
+ VALUES (%s, %s, %s)
351
+ ON DUPLICATE KEY UPDATE
352
+ name = VALUES(name),
353
+ email = VALUES(email);
354
+
355
+ Actual execution with bound values:
356
+ [(1, 'Pablo', 'pablo@example.com'),
357
+ (2, 'Marina', 'marina@example.com')]
358
+
359
+ Notes
360
+ -----
361
+ - Primary key column is excluded from the UPDATE clause
362
+ - Uses MySQL's VALUES() function to reference inserted values
363
+ - For batch inserts, all rows must have the same column structure
364
+ """
365
+
366
+ # Generate the INSERT portion of the query
367
+ query_insert = self.visit_insert(upsert)
368
+
369
+ # MySQL uses VALUES() function to reference inserted values in UPDATE clause
370
+ VALUES_FUNCTION = "VALUES"
371
+
372
+ # Validate that we have values to work with
373
+ if not upsert.values or len(upsert.values) == 0:
374
+ raise ValueError("Upsert requires at least one value dictionary")
375
+
376
+ # Get column information from first value (all rows should have same structure)
377
+ first_value = upsert.values[0]
378
+ columns = first_value.get_columns()
379
+ pk_column = first_value.get_pk().column_name
380
+
381
+ # Build UPDATE clause: exclude primary key from updates
382
+ # Format: column = VALUES(column)
383
+ # fmt: off
384
+ update_columns = [
385
+ f"{col} = {VALUES_FUNCTION}({col})"
386
+ for col in columns
387
+ if col != pk_column
388
+ ]
389
+ # fmt: on
390
+
391
+ # Handle edge case: if only PK exists, nothing to update
392
+ if not update_columns:
393
+ raise ValueError(f"No columns to update besides primary key '{pk_column}'. " "UPSERT requires at least one non-PK column.")
394
+
395
+ update_clause = ", ".join(update_columns)
396
+
397
+ return f"{query_insert} ON DUPLICATE KEY UPDATE {update_clause};"
205
398
 
206
399
  def visit_update(self, update: Update, **kw) -> str:
207
- pass
400
+ class UpdateKeyError(KeyError):
401
+ def __init__(self, table: Type[Table], key: str | ColumnType, *args):
402
+ super().__init__(*args)
403
+ self._table: Type[Table] = table
404
+ self._key: str | ColumnType = key
405
+
406
+ def __str__(self):
407
+ BASE_MSSG = lambda col: f"The column '{col}' does not belong to the table '{self._table.__table_name__}'" # noqa: E731
408
+
409
+ if isinstance(self._key, Column):
410
+ return BASE_MSSG(self._key.column_name) + f"; it belongs to the table '{self._key.table.__table_name__}'. Please check the columns in the query."
411
+ return BASE_MSSG(self._key) + ". Please check the columns in the query."
412
+
413
+ def __is_valid__(col: Column) -> bool:
414
+ if update.table is not col.table:
415
+ raise UpdateKeyError(update.table, col)
416
+ return not col.is_auto_generated
417
+
418
+ if not isinstance(update.values, dict):
419
+ raise TypeError
420
+
421
+ col_names: list[Column] = []
422
+ CASTER = self.dialect.caster()
423
+ for col, value in update.values.items():
424
+ if isinstance(col, str):
425
+ if not hasattr(update.table, col):
426
+ raise UpdateKeyError(update.table, col)
427
+ col = getattr(update.table, col)
428
+ if not isinstance(col, Column | ColumnProxy):
429
+ raise ValueError
430
+
431
+ if __is_valid__(col):
432
+ clean_data = CASTER.for_value(value)
433
+ col_names.append((col.column_name, clean_data.wildcard_to_insert()))
434
+ update.cleaned_values.append(clean_data.to_database)
435
+
436
+ set_query: str = ",".join(["=".join(col_data) for col_data in col_names])
437
+
438
+ query = f"UPDATE {update.table.__table_name__} SET {set_query}"
439
+ update.cleaned_values = tuple(update.cleaned_values)
440
+ return query
208
441
 
209
442
  def visit_offset(self, offset: Offset, **kw) -> str:
210
443
  return f"OFFSET {offset._number}"
@@ -221,20 +454,6 @@ class MySQLCompiler(compiler.SQLCompiler):
221
454
 
222
455
  return ClauseInfo.concat_alias_and_column(f"COUNT({column})", count.alias)
223
456
 
224
- def visit_where(self, where: Where) -> str:
225
- from ormlambda.sql.comparer import Comparer
226
-
227
- if not where.comparer:
228
- return ""
229
- cols = Comparer.join_comparers(list(where.comparer), restrictive=where.restrictive, dialect=self.dialect)
230
- return f"WHERE {cols}"
231
-
232
- def visit_having(self, having: Having) -> str:
233
- from ormlambda.sql.comparer import Comparer
234
-
235
- cols = Comparer.join_comparers(list(having.comparer), restrictive=having.restrictive, dialect=self.dialect, table=None, literal=True)
236
- return f"HAVING {cols}"
237
-
238
457
  def visit_order(self, order: Order, **kw) -> str:
239
458
  ORDER = "ORDER BY"
240
459
  string_columns: list[str] = []
@@ -285,25 +504,60 @@ class MySQLCompiler(compiler.SQLCompiler):
285
504
  )
286
505
  return clause_info.query(self.dialect)
287
506
 
288
- def visit_max(self, max: Max, **kw) -> str:
507
+ def visit_max(self, obj: Max, **kw) -> str:
508
+ return self._compile_aggregate_method("MAX", obj, **kw)
509
+
510
+ def visit_min(self, obj: Min, **kw) -> str:
511
+ return self._compile_aggregate_method("MIN", obj, **kw)
512
+
513
+ def visit_sum(self, obj: Sum, **kw) -> str:
514
+ return self._compile_aggregate_method("SUM", obj, **kw)
515
+
516
+ def visit_avg(self, obj: Avg, **kw) -> str:
517
+ return self._compile_aggregate_method("AVG", obj, **kw)
518
+
519
+ def visit_abs(self, obj: Abs, **kw) -> str:
520
+ return self._compile_aggregate_method("ABS", obj, **kw)
521
+
522
+ def visit_ceil(self, obj: Ceil, **kw) -> str:
523
+ return self._compile_aggregate_method("CEIL", obj, **kw)
524
+
525
+ def visit_floor(self, obj: Floor, **kw) -> str:
526
+ return self._compile_aggregate_method("FLOOR", obj, **kw)
527
+
528
+ def visit_round(self, obj: Round, **kw) -> str:
529
+ return self._compile_aggregate_method("ROUND", obj, **kw)
530
+
531
+ def visit_pow(self, obj: Pow, **kw) -> str:
289
532
  attr = {**kw, "alias_clause": None}
290
- column = max.column.compile(self.dialect, **attr).string
291
- return ClauseInfo.concat_alias_and_column(f"MAX({column})", max.alias)
533
+ column = obj.column.compile(self.dialect, **attr).string
534
+ return ClauseInfo.concat_alias_and_column(f"POW({column}, {obj._exponent})", obj.alias)
535
+
536
+ def visit_sqrt(self, obj: Sqrt, **kw) -> str:
537
+ return self._compile_aggregate_method("SQRT", obj, **kw)
538
+
539
+ def visit_mod(self, obj: Mod, **kw) -> str:
540
+ attr = {**kw, "alias_clause": None}
541
+ column = obj.column.compile(self.dialect, **attr).string
542
+ return ClauseInfo.concat_alias_and_column(f"MOD({column}, {obj._divisor})", obj.alias)
543
+
544
+ def visit_rand(self, obj: Rand, **kw) -> str:
545
+ return self._compile_aggregate_method("RAND", obj, **kw)
292
546
 
293
- def visit_min(self, min: Min, **kw) -> str:
547
+ def visit_truncate(self, obj: Truncate, **kw) -> str:
294
548
  attr = {**kw, "alias_clause": None}
295
- column = min.column.compile(self.dialect, **attr).string
296
- return ClauseInfo.concat_alias_and_column(f"MIN({column})", min.alias)
549
+ column = obj.column.compile(self.dialect, **attr).string
550
+ return ClauseInfo.concat_alias_and_column(f"TRUNCATE({column}, {obj._decimal})", obj.alias)
297
551
 
298
- def visit_sum(self, sum: Sum, **kw) -> str:
552
+ def _compile_aggregate_method(self, name: str, function: IFunction, **kw) -> str:
299
553
  attr = {**kw, "alias_clause": None}
300
- column = sum.column.compile(self.dialect, **attr).string
301
- return ClauseInfo.concat_alias_and_column(f"SUM({column})", sum.alias)
554
+ column = function.column.compile(self.dialect, **attr).string
555
+ return ClauseInfo.concat_alias_and_column(f"{name}({column})", function.alias)
302
556
 
303
557
  def visit_st_astext(self, st_astext: ST_AsText) -> str:
304
- # avoid use placeholder when using IAggregate because no make sense.
558
+ # avoid use placeholder when using IFunction because no make sense.
305
559
  if st_astext.alias and (found := ClauseInfo._keyRegex.findall(st_astext.alias)):
306
- raise NotKeysInIAggregateError(found)
560
+ raise NotKeysInIFunctionError(found)
307
561
  return ClauseInfo.concat_alias_and_column(f"ST_AsText({st_astext.column.compile(self.dialect, alias_clause=None).string})", st_astext.alias)
308
562
 
309
563
  def visit_st_contains(self, st_contains: ST_Contains) -> str:
@@ -29,4 +29,8 @@ class IterableCaster[TType](JsonCaster[TType]):
29
29
  @property
30
30
  @BaseCaster.return_value_if_exists
31
31
  def string_data(self) -> Optional[str]:
32
- return str(self.value)
32
+ value = tuple(self.value) if not isinstance(self.value, tuple) else self.value
33
+
34
+ if len(value) == 1:
35
+ return f"({value[0]})"
36
+ return str(value)
@@ -1,11 +1,11 @@
1
1
  from __future__ import annotations
2
- from ormlambda.sql.clause_info import IAggregate
2
+ from ormlambda.sql.functions.interface import IFunction
3
3
  from ormlambda.sql.types import ColumnType, AliasType
4
4
 
5
5
  from ormlambda.sql.elements import ClauseElement
6
6
 
7
7
 
8
- class ST_AsText[T, TProp](ClauseElement, IAggregate):
8
+ class ST_AsText[T, TProp](ClauseElement, IFunction):
9
9
  """
10
10
  https://dev.mysql.com/doc/refman/8.4/en/fetching-spatial-data.html
11
11
 
@@ -5,11 +5,11 @@ from shapely import Point
5
5
 
6
6
  from ormlambda import Column
7
7
  from ormlambda.sql.types import ColumnType, AliasType
8
- from ormlambda.sql.clause_info import IAggregate
8
+ from ormlambda.sql.functions.interface import IFunction
9
9
  from ormlambda.sql.elements import ClauseElement
10
10
 
11
11
 
12
- class ST_Contains(ClauseElement, IAggregate):
12
+ class ST_Contains(ClauseElement, IFunction):
13
13
  __visit_name__ = "st_contains"
14
14
 
15
15
  def __init__[TProp: Column](
@@ -17,10 +17,10 @@ class NoSuchModuleError(Exception):
17
17
  return f"NoSuchModuleError: {self.args[0]}"
18
18
 
19
19
 
20
- class DuplicatedClauseName(Exception):
20
+ class DuplicatedClauseNameError(Exception):
21
21
  def __init__(self, names: tuple[str], **kw):
22
22
  self.names = names
23
23
  super().__init__(**kw)
24
24
 
25
25
  def __str__(self):
26
- return f"Some clauses has the same alias. {self.names}\nTry wrapping the clause with the 'Alias' class first or setting 'avoid_duplicate' param as 'True'"
26
+ return f"Some clauses has the same alias. {self.names}\nTry wrapping the clause with the 'Alias' class first or setting 'avoid_duplicates' param as 'True'"
@@ -130,7 +130,8 @@ class Response[TFlavour, *Ts]:
130
130
  for i, data in enumerate(row):
131
131
  alias = self._columns[i]
132
132
  clause = self._select[alias]
133
- parse_data = self._caster.for_value(data, value_type=clause.dtype).from_database
133
+ dtype = clause.dtype if hasattr(clause, "dtype") else None
134
+ parse_data = self._caster.for_value(data, value_type=dtype).from_database
134
135
  new_row.append(parse_data)
135
136
  new_row = tuple(new_row)
136
137
  if not isinstance(new_row, tuple):
@@ -1,2 +1 @@
1
- from .interface import IAggregate # noqa: F401
2
1
  from .clause_info import ClauseInfo # noqa: F401