pytrilogy 0.0.2.30__tar.gz → 0.0.2.32__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.

Potentially problematic release.


This version of pytrilogy might be problematic. Click here for more details.

Files changed (108) hide show
  1. {pytrilogy-0.0.2.30/pytrilogy.egg-info → pytrilogy-0.0.2.32}/PKG-INFO +1 -1
  2. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32/pytrilogy.egg-info}/PKG-INFO +1 -1
  3. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/tests/test_functions.py +21 -0
  4. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/tests/test_parsing.py +11 -0
  5. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/__init__.py +1 -1
  6. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/dialect/base.py +8 -6
  7. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/dialect/common.py +2 -1
  8. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/parsing/parse_engine.py +23 -10
  9. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/parsing/trilogy.lark +9 -9
  10. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/LICENSE.md +0 -0
  11. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/README.md +0 -0
  12. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/pyproject.toml +0 -0
  13. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/pytrilogy.egg-info/SOURCES.txt +0 -0
  14. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/pytrilogy.egg-info/dependency_links.txt +0 -0
  15. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/pytrilogy.egg-info/entry_points.txt +0 -0
  16. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/pytrilogy.egg-info/requires.txt +0 -0
  17. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/pytrilogy.egg-info/top_level.txt +0 -0
  18. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/setup.cfg +0 -0
  19. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/setup.py +0 -0
  20. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/tests/test_datatypes.py +0 -0
  21. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/tests/test_declarations.py +0 -0
  22. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/tests/test_derived_concepts.py +0 -0
  23. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/tests/test_discovery_nodes.py +0 -0
  24. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/tests/test_environment.py +0 -0
  25. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/tests/test_executor.py +0 -0
  26. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/tests/test_imports.py +0 -0
  27. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/tests/test_metadata.py +0 -0
  28. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/tests/test_models.py +0 -0
  29. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/tests/test_multi_join_assignments.py +0 -0
  30. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/tests/test_partial_handling.py +0 -0
  31. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/tests/test_query_processing.py +0 -0
  32. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/tests/test_select.py +0 -0
  33. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/tests/test_show.py +0 -0
  34. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/tests/test_statements.py +0 -0
  35. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/tests/test_undefined_concept.py +0 -0
  36. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/tests/test_where_clause.py +0 -0
  37. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/compiler.py +0 -0
  38. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/constants.py +0 -0
  39. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/core/__init__.py +0 -0
  40. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/core/constants.py +0 -0
  41. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/core/enums.py +0 -0
  42. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/core/env_processor.py +0 -0
  43. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/core/environment_helpers.py +0 -0
  44. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/core/ergonomics.py +0 -0
  45. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/core/exceptions.py +0 -0
  46. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/core/functions.py +0 -0
  47. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/core/graph_models.py +0 -0
  48. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/core/internal.py +0 -0
  49. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/core/models.py +0 -0
  50. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/core/optimization.py +0 -0
  51. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/core/optimizations/__init__.py +0 -0
  52. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/core/optimizations/base_optimization.py +0 -0
  53. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/core/optimizations/inline_constant.py +0 -0
  54. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/core/optimizations/inline_datasource.py +0 -0
  55. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/core/optimizations/predicate_pushdown.py +0 -0
  56. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/core/processing/__init__.py +0 -0
  57. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/core/processing/concept_strategies_v3.py +0 -0
  58. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/core/processing/graph_utils.py +0 -0
  59. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/core/processing/node_generators/__init__.py +0 -0
  60. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/core/processing/node_generators/basic_node.py +0 -0
  61. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/core/processing/node_generators/common.py +0 -0
  62. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/core/processing/node_generators/filter_node.py +0 -0
  63. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/core/processing/node_generators/group_node.py +0 -0
  64. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/core/processing/node_generators/group_to_node.py +0 -0
  65. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/core/processing/node_generators/multiselect_node.py +0 -0
  66. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/core/processing/node_generators/node_merge_node.py +0 -0
  67. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/core/processing/node_generators/rowset_node.py +0 -0
  68. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/core/processing/node_generators/select_merge_node.py +0 -0
  69. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/core/processing/node_generators/select_node.py +0 -0
  70. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/core/processing/node_generators/unnest_node.py +0 -0
  71. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/core/processing/node_generators/window_node.py +0 -0
  72. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/core/processing/nodes/__init__.py +0 -0
  73. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/core/processing/nodes/base_node.py +0 -0
  74. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/core/processing/nodes/filter_node.py +0 -0
  75. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/core/processing/nodes/group_node.py +0 -0
  76. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/core/processing/nodes/merge_node.py +0 -0
  77. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/core/processing/nodes/select_node_v2.py +0 -0
  78. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/core/processing/nodes/unnest_node.py +0 -0
  79. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/core/processing/nodes/window_node.py +0 -0
  80. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/core/processing/utility.py +0 -0
  81. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/core/query_processor.py +0 -0
  82. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/dialect/__init__.py +0 -0
  83. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/dialect/bigquery.py +0 -0
  84. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/dialect/config.py +0 -0
  85. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/dialect/duckdb.py +0 -0
  86. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/dialect/enums.py +0 -0
  87. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/dialect/postgres.py +0 -0
  88. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/dialect/presto.py +0 -0
  89. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/dialect/snowflake.py +0 -0
  90. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/dialect/sql_server.py +0 -0
  91. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/engine.py +0 -0
  92. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/executor.py +0 -0
  93. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/hooks/__init__.py +0 -0
  94. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/hooks/base_hook.py +0 -0
  95. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/hooks/graph_hook.py +0 -0
  96. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/hooks/query_debugger.py +0 -0
  97. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/metadata/__init__.py +0 -0
  98. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/parser.py +0 -0
  99. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/parsing/__init__.py +0 -0
  100. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/parsing/common.py +0 -0
  101. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/parsing/config.py +0 -0
  102. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/parsing/exceptions.py +0 -0
  103. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/parsing/helpers.py +0 -0
  104. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/parsing/render.py +0 -0
  105. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/py.typed +0 -0
  106. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/scripts/__init__.py +0 -0
  107. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/scripts/trilogy.py +0 -0
  108. {pytrilogy-0.0.2.30 → pytrilogy-0.0.2.32}/trilogy/utility.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pytrilogy
3
- Version: 0.0.2.30
3
+ Version: 0.0.2.32
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pytrilogy
3
- Version: 0.0.2.30
3
+ Version: 0.0.2.32
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -212,6 +212,27 @@ def test_case_function(test_environment):
212
212
  assert test_environment.concepts["test_upper_case"].datatype == DataType.BOOL
213
213
 
214
214
 
215
+ def test_case_like_function(test_environment):
216
+ declarations = """
217
+ property test_like <- CASE WHEN category_name like '%abc%' then True else False END;
218
+ select
219
+ category_name,
220
+ test_like
221
+ ;"""
222
+ env, parsed = parse(declarations, environment=test_environment)
223
+ assert (
224
+ test_environment.concepts["category_name"]
225
+ in test_environment.concepts["test_like"].lineage.concept_arguments
226
+ )
227
+ select: SelectStatement = parsed[-1]
228
+ for dialect in TEST_DIALECTS:
229
+ compiled = dialect.compile_statement(process_query(test_environment, select))
230
+ assert "CASE" in compiled
231
+ assert "ELSE" in compiled
232
+ assert "END" in compiled
233
+ assert test_environment.concepts["test_like"].datatype == DataType.BOOL
234
+
235
+
215
236
  def test_split_and_index_function(test_environment):
216
237
  declarations = """
217
238
  constant test_string <- 'abc_def';
@@ -422,6 +422,17 @@ select
422
422
  assert env.concepts["labels"].datatype.value_type == DataType.INTEGER
423
423
 
424
424
 
425
+ def test_empty_string():
426
+ env, parsed = parse_text(
427
+ """
428
+ const labels <- '';
429
+
430
+
431
+ """
432
+ )
433
+ assert env.concepts["labels"].datatype == DataType.STRING
434
+
435
+
425
436
  def test_struct_attr_access():
426
437
 
427
438
  text = """
@@ -4,6 +4,6 @@ from trilogy.executor import Executor
4
4
  from trilogy.parser import parse
5
5
  from trilogy.constants import CONFIG
6
6
 
7
- __version__ = "0.0.2.30"
7
+ __version__ = "0.0.2.32"
8
8
 
9
9
  __all__ = ["parse", "Executor", "Dialects", "Environment", "CONFIG"]
@@ -646,12 +646,14 @@ class BaseDialect:
646
646
  else None
647
647
  ),
648
648
  group_by=(
649
- list(
650
- set(
651
- [
652
- self.render_concept_sql(c, cte, alias=False)
653
- for c in cte.group_concepts
654
- ]
649
+ sorted(
650
+ list(
651
+ set(
652
+ [
653
+ self.render_concept_sql(c, cte, alias=False)
654
+ for c in cte.group_concepts
655
+ ]
656
+ )
655
657
  )
656
658
  )
657
659
  if cte.group_to_grain
@@ -101,5 +101,6 @@ def render_join(
101
101
  )
102
102
  if not base_joinkeys:
103
103
  base_joinkeys = ["1=1"]
104
- joinkeys = " AND ".join(base_joinkeys)
104
+
105
+ joinkeys = " AND ".join(sorted(base_joinkeys))
105
106
  return f"{join.jointype.value.upper()} JOIN {right_base} on {joinkeys}"
@@ -162,6 +162,21 @@ def parse_concept_reference(
162
162
  return lookup, namespace, name, parent
163
163
 
164
164
 
165
+ def expr_to_boolean(
166
+ root,
167
+ ) -> Union[Comparison, SubselectComparison, Conditional]:
168
+ if not isinstance(root, (Comparison, SubselectComparison, Conditional)):
169
+ if arg_to_datatype(root) == DataType.BOOL:
170
+ root = Comparison(left=root, right=True, operator=ComparisonOperator.EQ)
171
+ else:
172
+ root = Comparison(
173
+ left=root,
174
+ right=MagicConstants.NULL,
175
+ operator=ComparisonOperator.IS_NOT,
176
+ )
177
+ return root
178
+
179
+
165
180
  def unwrap_transformation(
166
181
  input: Union[
167
182
  FilterItem,
@@ -1022,15 +1037,7 @@ class ParseToObjects(Transformer):
1022
1037
 
1023
1038
  def where(self, args):
1024
1039
  root = args[0]
1025
- if not isinstance(root, (Comparison, Conditional, Parenthetical)):
1026
- if arg_to_datatype(root) == DataType.BOOL:
1027
- root = Comparison(left=root, right=True, operator=ComparisonOperator.EQ)
1028
- else:
1029
- root = Comparison(
1030
- left=root,
1031
- right=MagicConstants.NULL,
1032
- operator=ComparisonOperator.IS_NOT,
1033
- )
1040
+ root = expr_to_boolean(root)
1034
1041
  return WhereClause(conditional=root)
1035
1042
 
1036
1043
  def having(self, args):
@@ -1091,6 +1098,11 @@ class ParseToObjects(Transformer):
1091
1098
  def tuple_lit(self, args):
1092
1099
  return tuple_to_wrapper(args)
1093
1100
 
1101
+ def string_lit(self, args) -> str:
1102
+ if not args:
1103
+ return ""
1104
+ return args[0]
1105
+
1094
1106
  def struct_lit(self, args):
1095
1107
 
1096
1108
  zipped = dict(zip(args[::2], args[1::2]))
@@ -1838,7 +1850,8 @@ class ParseToObjects(Transformer):
1838
1850
  @v_args(meta=True)
1839
1851
  def fcase_when(self, meta, args) -> CaseWhen:
1840
1852
  args = process_function_args(args, meta=meta, environment=self.environment)
1841
- return CaseWhen(comparison=args[0], expr=args[1])
1853
+ root = expr_to_boolean(args[0])
1854
+ return CaseWhen(comparison=root, expr=args[1])
1842
1855
 
1843
1856
  @v_args(meta=True)
1844
1857
  def fcase_else(self, meta, args) -> CaseElse:
@@ -84,7 +84,7 @@
84
84
 
85
85
  COPY_TYPE: "csv"i
86
86
 
87
- copy_statement: "copy"i "into"i COPY_TYPE _string_lit "from"i select_statement
87
+ copy_statement: "copy"i "into"i COPY_TYPE string_lit "from"i select_statement
88
88
 
89
89
  // FUNCTION blocks
90
90
  function: raw_function
@@ -118,7 +118,7 @@
118
118
  _assignment: ("->") | "as"i
119
119
  select_transform : expr _assignment IDENTIFIER metadata?
120
120
 
121
- metadata: "metadata" "(" IDENTIFIER "=" _string_lit ")"
121
+ metadata: "metadata" "(" IDENTIFIER "=" string_lit ")"
122
122
 
123
123
  limit: "LIMIT"i /[0-9]+/
124
124
 
@@ -169,8 +169,8 @@
169
169
  unnest: _UNNEST expr ")"
170
170
  //indexing into an expression is a function
171
171
  index_access: expr "[" int_lit "]"
172
- map_key_access: expr "[" _string_lit "]"
173
- attr_access: expr "." _string_lit
172
+ map_key_access: expr "[" string_lit "]"
173
+ attr_access: expr "." string_lit
174
174
 
175
175
  expr: _constant_functions | window_item | filter_item | subselect_comparison | between_comparison | fgroup | aggregate_functions | unnest | _static_functions | literal | concept_lit | index_access | map_key_access | attr_access | parenthetical | expr_tuple | comparison | alt_like
176
176
 
@@ -209,16 +209,16 @@
209
209
 
210
210
  //string
211
211
  _LIKE.1: "like("i
212
- like: _LIKE expr "," _string_lit ")"
212
+ like: _LIKE expr "," string_lit ")"
213
213
  _ILIKE.1: "ilike("i
214
- ilike: _ILIKE expr "," _string_lit ")"
214
+ ilike: _ILIKE expr "," string_lit ")"
215
215
  alt_like: expr "like"i expr
216
216
  _UPPER.1: "upper("i
217
217
  upper: _UPPER expr ")"
218
218
  _LOWER.1: "lower("i
219
219
  lower: _LOWER expr ")"
220
220
  _SPLIT.1: "split("i
221
- fsplit: _SPLIT expr "," _string_lit ")"
221
+ fsplit: _SPLIT expr "," string_lit ")"
222
222
  _STRPOS.1: "strpos("i
223
223
  fstrpos: _STRPOS expr "," expr ")"
224
224
  _SUBSTRING.1: "substring("i
@@ -301,7 +301,7 @@
301
301
  SINGLE_STRING_CHARS: /(?:(?!\${)([^'\\]|\\.))+/+ // any character except '
302
302
  _single_quote: "'" ( SINGLE_STRING_CHARS )* "'"
303
303
  _double_quote: "\"" ( DOUBLE_STRING_CHARS )* "\""
304
- _string_lit: _single_quote | _double_quote
304
+ string_lit: _single_quote | _double_quote
305
305
 
306
306
  MINUS: "-"
307
307
 
@@ -322,7 +322,7 @@
322
322
 
323
323
  !null_lit.1: "null"i
324
324
 
325
- literal: null_lit | _string_lit | int_lit | float_lit | bool_lit | array_lit | map_lit | struct_lit | tuple_lit
325
+ literal: null_lit | string_lit | int_lit | float_lit | bool_lit | array_lit | map_lit | struct_lit | tuple_lit
326
326
 
327
327
  MODIFIER: "Optional"i | "Partial"i | "Nullable"i
328
328
 
File without changes
File without changes
File without changes
File without changes