codegraph-engine 2.2.1__py3-none-any.whl → 3.0.0__py3-none-any.whl
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.
- codegraph/__init__.py +1 -1
- codegraph/agent_brain.py +172 -2
- codegraph/agent_capabilities.py +564 -6
- codegraph/agent_rules.py +1 -1
- codegraph/api_drift.py +455 -0
- codegraph/async_queue/__init__.py +50 -0
- codegraph/async_queue/detectors/__init__.py +48 -0
- codegraph/async_queue/detectors/celery.py +160 -0
- codegraph/async_queue/detectors/kafka.py +168 -0
- codegraph/async_queue/detectors/rabbitmq.py +116 -0
- codegraph/async_queue/detectors/redis.py +130 -0
- codegraph/async_queue/detectors/sqs.py +135 -0
- codegraph/async_queue/models.py +161 -0
- codegraph/async_queue/otel_models.py +74 -0
- codegraph/async_queue/otel_parser.py +337 -0
- codegraph/async_queue/reconciler.py +427 -0
- codegraph/async_queue/scanner.py +291 -0
- codegraph/async_queue/traversal.py +447 -0
- codegraph/branch_comparison.py +197 -0
- codegraph/change_impact.py +547 -0
- codegraph/cli.py +992 -3
- codegraph/cli_output.py +17 -2
- codegraph/context.py +38 -11
- codegraph/context_freshness.py +351 -0
- codegraph/database/__init__.py +14 -0
- codegraph/database/extractor.py +712 -17
- codegraph/database/interrogation.py +388 -2
- codegraph/database/schema_drift.py +862 -0
- codegraph/epistemic.py +7 -0
- codegraph/evidence_contract.py +21 -0
- codegraph/frameworks.py +171 -6
- codegraph/frameworks_nestjs.py +502 -0
- codegraph/frameworks_nextjs.py +337 -0
- codegraph/frameworks_react.py +457 -0
- codegraph/freshness.py +2 -0
- codegraph/frontend_html.py +109 -0
- codegraph/git.py +2 -0
- codegraph/git_state.py +545 -0
- codegraph/indexing/__init__.py +7 -1
- codegraph/indexing/indexer.py +291 -38
- codegraph/indexing/models.py +6 -0
- codegraph/indexing/parser.py +239 -0
- codegraph/indexing/pool.py +136 -0
- codegraph/indexing/scanner.py +6 -0
- codegraph/indexing/templates.py +418 -0
- codegraph/interrogation.py +128 -20
- codegraph/mcp/server.py +258 -27
- codegraph/models.py +7 -0
- codegraph/monorepo.py +3 -0
- codegraph/optimizer.py +42 -5
- codegraph/process_lifecycle.py +31 -13
- codegraph/refactor/__init__.py +36 -0
- codegraph/refactor/models.py +114 -0
- codegraph/refactor/renamer.py +844 -0
- codegraph/refactor/transactions.py +403 -0
- codegraph/resolver.py +1202 -159
- codegraph/retrieval_policy.py +6 -0
- codegraph/route_topology.py +172 -0
- codegraph/runtime/__init__.py +19 -0
- codegraph/runtime/collector.py +155 -0
- codegraph/runtime/db_watcher.py +191 -0
- codegraph/runtime/ingestor.py +30 -10
- codegraph/runtime/port_inspector.py +304 -0
- codegraph/runtime/runner.py +155 -6
- codegraph/runtime/trace_linker.py +175 -0
- codegraph/search/hybrid.py +26 -0
- codegraph/semantic_conflicts.py +270 -0
- codegraph/semantic_decorators.py +34 -3
- codegraph/structural_diff.py +542 -0
- codegraph/styles.py +144 -0
- codegraph/symbol_history.py +277 -0
- codegraph/tsconfig.py +236 -0
- codegraph/ui/__init__.py +6 -0
- codegraph/ui/server.py +715 -0
- codegraph/watcher.py +323 -0
- {codegraph_engine-2.2.1.dist-info → codegraph_engine-3.0.0.dist-info}/METADATA +257 -39
- codegraph_engine-3.0.0.dist-info/RECORD +129 -0
- codegraph_engine-2.2.1.dist-info/RECORD +0 -87
- {codegraph_engine-2.2.1.dist-info → codegraph_engine-3.0.0.dist-info}/WHEEL +0 -0
- {codegraph_engine-2.2.1.dist-info → codegraph_engine-3.0.0.dist-info}/entry_points.txt +0 -0
- {codegraph_engine-2.2.1.dist-info → codegraph_engine-3.0.0.dist-info}/licenses/LICENSE +0 -0
- {codegraph_engine-2.2.1.dist-info → codegraph_engine-3.0.0.dist-info}/top_level.txt +0 -0
codegraph/database/extractor.py
CHANGED
|
@@ -521,7 +521,7 @@ def has_potential_orm_models(content: str, file_path: str, language: str = "pyth
|
|
|
521
521
|
if language == "python" or file_path.endswith(".py"):
|
|
522
522
|
return any(tok in content for tok in _PYTHON_ORM_MODEL_TOKENS)
|
|
523
523
|
if language in ("javascript", "typescript") or file_path.endswith((".js", ".jsx", ".ts", ".tsx")):
|
|
524
|
-
return any(tok in content for tok in ("Entity", "define(", ".init(", "model(", "Schema("))
|
|
524
|
+
return any(tok in content for tok in ("Entity", "define(", ".init(", "model(", "Schema(", "pgTable", "mysqlTable", "sqliteTable"))
|
|
525
525
|
return False
|
|
526
526
|
|
|
527
527
|
|
|
@@ -1294,7 +1294,11 @@ def _record_sqlalchemy_column(
|
|
|
1294
1294
|
if acname == "ForeignKey" and arg.args:
|
|
1295
1295
|
fk_target = _ast_const_str(arg.args[0])
|
|
1296
1296
|
else:
|
|
1297
|
-
|
|
1297
|
+
if arg.args and isinstance(arg.args[0], (ast.Constant, ast.UnaryOp)):
|
|
1298
|
+
arg_str = _ast_const_str(arg.args[0]) or (str(arg.args[0].value) if isinstance(arg.args[0], ast.Constant) else "")
|
|
1299
|
+
data_type = f"{acname}({arg_str})" if arg_str else acname
|
|
1300
|
+
else:
|
|
1301
|
+
data_type = acname
|
|
1298
1302
|
|
|
1299
1303
|
col_cid = build_db_canonical_id(
|
|
1300
1304
|
DatabaseEntityKind.COLUMN,
|
|
@@ -1683,6 +1687,12 @@ def _record_django_field(
|
|
|
1683
1687
|
dialect=dialect,
|
|
1684
1688
|
schema=schema,
|
|
1685
1689
|
)
|
|
1690
|
+
max_len_node = _get_kwarg(call_node, "max_length")
|
|
1691
|
+
effective_data_type = field_type
|
|
1692
|
+
if max_len_node is not None:
|
|
1693
|
+
if isinstance(max_len_node, ast.Constant) and isinstance(max_len_node.value, int):
|
|
1694
|
+
effective_data_type = f"{field_type}({max_len_node.value})"
|
|
1695
|
+
|
|
1686
1696
|
res.entities.append(
|
|
1687
1697
|
DatabaseEntity(
|
|
1688
1698
|
canonical_id=col_cid,
|
|
@@ -1692,7 +1702,7 @@ def _record_django_field(
|
|
|
1692
1702
|
schema_name=schema,
|
|
1693
1703
|
table_name=table_name,
|
|
1694
1704
|
column_name=col_name,
|
|
1695
|
-
data_type=
|
|
1705
|
+
data_type=effective_data_type,
|
|
1696
1706
|
nullable=nullable,
|
|
1697
1707
|
is_primary_key=is_pk,
|
|
1698
1708
|
is_unique=is_unique,
|
|
@@ -2813,9 +2823,14 @@ def _extract_prisma_schema(
|
|
|
2813
2823
|
index_generation: int,
|
|
2814
2824
|
res: DatabaseExtractionResult,
|
|
2815
2825
|
) -> None:
|
|
2826
|
+
ds_m = re.search(r'datasource\s+\w+\s*\{[\s\S]*?provider\s*=\s*"([^"]+)"', content)
|
|
2827
|
+
if ds_m:
|
|
2828
|
+
dialect = normalize_dialect(ds_m.group(1))
|
|
2829
|
+
|
|
2816
2830
|
for m in re.finditer(r"model\s+([A-Za-z_][A-Za-z0-9_]*)\s*\{([\s\S]*?)\}", content):
|
|
2817
2831
|
model_name = m.group(1)
|
|
2818
2832
|
body = m.group(2)
|
|
2833
|
+
model_line = content[: m.start()].count("\n") + 1
|
|
2819
2834
|
map_m = re.search(r'@@map\(\s*"([^"]+)"\s*\)', body)
|
|
2820
2835
|
tbl_name = (map_m.group(1) if map_m else _pluralize_table(_camel_to_snake(model_name))).lower()
|
|
2821
2836
|
tbl_cid = build_db_canonical_id(
|
|
@@ -2836,8 +2851,8 @@ def _extract_prisma_schema(
|
|
|
2836
2851
|
orm_model_id=model_cid,
|
|
2837
2852
|
framework="prisma",
|
|
2838
2853
|
file_path=file_path,
|
|
2839
|
-
start_line=
|
|
2840
|
-
end_line=
|
|
2854
|
+
start_line=model_line,
|
|
2855
|
+
end_line=model_line,
|
|
2841
2856
|
evidence=f"model {model_name}",
|
|
2842
2857
|
evidence_class="FRAMEWORK_VERIFIED",
|
|
2843
2858
|
)
|
|
@@ -2853,8 +2868,8 @@ def _extract_prisma_schema(
|
|
|
2853
2868
|
orm_model_id=model_cid,
|
|
2854
2869
|
framework="prisma",
|
|
2855
2870
|
file_path=file_path,
|
|
2856
|
-
start_line=
|
|
2857
|
-
end_line=
|
|
2871
|
+
start_line=model_line,
|
|
2872
|
+
end_line=model_line,
|
|
2858
2873
|
evidence=f"model {model_name}",
|
|
2859
2874
|
evidence_class="FRAMEWORK_VERIFIED",
|
|
2860
2875
|
)
|
|
@@ -2867,8 +2882,8 @@ def _extract_prisma_schema(
|
|
|
2867
2882
|
evidence_class="FRAMEWORK_VERIFIED",
|
|
2868
2883
|
confidence="HIGH",
|
|
2869
2884
|
file=file_path,
|
|
2870
|
-
start_line=
|
|
2871
|
-
end_line=
|
|
2885
|
+
start_line=model_line,
|
|
2886
|
+
end_line=model_line,
|
|
2872
2887
|
evidence=f"model {model_name} -> {tbl_name}",
|
|
2873
2888
|
status="FACT",
|
|
2874
2889
|
parser_version=PARSER_VERSION,
|
|
@@ -2876,6 +2891,543 @@ def _extract_prisma_schema(
|
|
|
2876
2891
|
)
|
|
2877
2892
|
)
|
|
2878
2893
|
|
|
2894
|
+
# Pre-scan model body for @relation(fields: [...]) to accurately mark FK columns
|
|
2895
|
+
fk_fields: set[str] = set()
|
|
2896
|
+
for r_m in re.finditer(r'@relation\([\s\S]*?fields:\s*\[([A-Za-z0-9_,\s]+)\]', body):
|
|
2897
|
+
for fld in r_m.group(1).split(","):
|
|
2898
|
+
clean_fld = fld.strip()
|
|
2899
|
+
if clean_fld:
|
|
2900
|
+
fk_fields.add(clean_fld)
|
|
2901
|
+
|
|
2902
|
+
body_start_idx = m.start() + content[m.start() :].find("{") + 1
|
|
2903
|
+
current_offset = body_start_idx
|
|
2904
|
+
for line in body.splitlines():
|
|
2905
|
+
clean = line.strip()
|
|
2906
|
+
field_line = content[:current_offset].count("\n") + 1
|
|
2907
|
+
current_offset += len(line) + 1
|
|
2908
|
+
if not clean or clean.startswith(("//", "@@")):
|
|
2909
|
+
continue
|
|
2910
|
+
field_m = re.match(
|
|
2911
|
+
r"^([A-Za-z_][A-Za-z0-9_]*)\s+([A-Za-z0-9_]+)(\?)?(\[\])?\s*(.*)$",
|
|
2912
|
+
clean,
|
|
2913
|
+
)
|
|
2914
|
+
if not field_m:
|
|
2915
|
+
continue
|
|
2916
|
+
field_name = field_m.group(1)
|
|
2917
|
+
raw_type = field_m.group(2)
|
|
2918
|
+
is_optional = bool(field_m.group(3))
|
|
2919
|
+
field_attrs = field_m.group(5)
|
|
2920
|
+
|
|
2921
|
+
if raw_type not in (
|
|
2922
|
+
"String", "Int", "BigInt", "Float", "Decimal", "Boolean",
|
|
2923
|
+
"DateTime", "Json", "Bytes",
|
|
2924
|
+
) and "@relation" in field_attrs and "fields:" not in field_attrs:
|
|
2925
|
+
continue
|
|
2926
|
+
|
|
2927
|
+
map_field_m = re.search(r'@map\(\s*"([^"]+)"\s*\)', field_attrs)
|
|
2928
|
+
col_name = (map_field_m.group(1) if map_field_m else field_name).lower()
|
|
2929
|
+
|
|
2930
|
+
is_pk = bool("@id" in field_attrs or field_name == "id")
|
|
2931
|
+
is_unique = bool("@unique" in field_attrs)
|
|
2932
|
+
nullable = bool(is_optional)
|
|
2933
|
+
|
|
2934
|
+
def_m = re.search(r'@default\(([^)]*)\)', field_attrs)
|
|
2935
|
+
default_val = def_m.group(1).strip() if def_m else None
|
|
2936
|
+
|
|
2937
|
+
tgt_column = None
|
|
2938
|
+
fk_m = re.search(r'@relation\([\s\S]*?references:\s*\[([A-Za-z0-9_,\s]+)\]', field_attrs)
|
|
2939
|
+
if fk_m:
|
|
2940
|
+
tgt_col_raw = fk_m.group(1).split(",")[0].strip()
|
|
2941
|
+
tgt_column = tgt_col_raw
|
|
2942
|
+
|
|
2943
|
+
is_fk = bool(field_name in fk_fields or col_name in fk_fields or tgt_column)
|
|
2944
|
+
|
|
2945
|
+
col_cid = build_db_canonical_id(
|
|
2946
|
+
DatabaseEntityKind.COLUMN,
|
|
2947
|
+
table=tbl_name,
|
|
2948
|
+
column=col_name,
|
|
2949
|
+
dialect=dialect,
|
|
2950
|
+
schema=schema,
|
|
2951
|
+
)
|
|
2952
|
+
|
|
2953
|
+
res.entities.append(
|
|
2954
|
+
DatabaseEntity(
|
|
2955
|
+
canonical_id=col_cid,
|
|
2956
|
+
kind=DatabaseEntityKind.COLUMN.value,
|
|
2957
|
+
name=col_name,
|
|
2958
|
+
dialect=dialect,
|
|
2959
|
+
schema_name=schema,
|
|
2960
|
+
table_name=tbl_name,
|
|
2961
|
+
column_name=col_name,
|
|
2962
|
+
data_type=raw_type,
|
|
2963
|
+
nullable=nullable,
|
|
2964
|
+
default_value=default_val,
|
|
2965
|
+
is_primary_key=is_pk,
|
|
2966
|
+
is_foreign_key=is_fk,
|
|
2967
|
+
is_unique=is_unique,
|
|
2968
|
+
target_table=None,
|
|
2969
|
+
target_column=tgt_column,
|
|
2970
|
+
orm_model_id=model_cid,
|
|
2971
|
+
framework="prisma",
|
|
2972
|
+
file_path=file_path,
|
|
2973
|
+
start_line=field_line,
|
|
2974
|
+
end_line=field_line,
|
|
2975
|
+
evidence=clean,
|
|
2976
|
+
evidence_class="FRAMEWORK_VERIFIED",
|
|
2977
|
+
)
|
|
2978
|
+
)
|
|
2979
|
+
|
|
2980
|
+
res.edges.append(
|
|
2981
|
+
RelationshipRecord(
|
|
2982
|
+
source=tbl_cid,
|
|
2983
|
+
target=col_cid,
|
|
2984
|
+
relationship="MAPS_TO_COLUMN",
|
|
2985
|
+
evidence_class="FRAMEWORK_VERIFIED",
|
|
2986
|
+
confidence="HIGH",
|
|
2987
|
+
file=file_path,
|
|
2988
|
+
start_line=field_line,
|
|
2989
|
+
end_line=field_line,
|
|
2990
|
+
evidence=f"{tbl_name}.{col_name}",
|
|
2991
|
+
status="FACT",
|
|
2992
|
+
parser_version=PARSER_VERSION,
|
|
2993
|
+
index_generation=index_generation,
|
|
2994
|
+
)
|
|
2995
|
+
)
|
|
2996
|
+
|
|
2997
|
+
|
|
2998
|
+
_DRIZZLE_TABLE_RE = re.compile(
|
|
2999
|
+
r"\b(?:export\s+)?(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(pgTable|mysqlTable|sqliteTable)\s*\(\s*['\"`]([^'\"`]+)['\"`]\s*,\s*\{"
|
|
3000
|
+
)
|
|
3001
|
+
|
|
3002
|
+
|
|
3003
|
+
def _extract_drizzle_schema(
|
|
3004
|
+
content: str,
|
|
3005
|
+
file_path: str,
|
|
3006
|
+
module_name: str,
|
|
3007
|
+
dialect: str,
|
|
3008
|
+
schema: str,
|
|
3009
|
+
index_generation: int,
|
|
3010
|
+
res: DatabaseExtractionResult,
|
|
3011
|
+
) -> None:
|
|
3012
|
+
for m in _DRIZZLE_TABLE_RE.finditer(content):
|
|
3013
|
+
var_name = m.group(1)
|
|
3014
|
+
helper_fn = m.group(2)
|
|
3015
|
+
tbl_name = m.group(3).strip().lower()
|
|
3016
|
+
|
|
3017
|
+
start_pos = m.end() - 1
|
|
3018
|
+
depth = 0
|
|
3019
|
+
end_pos = start_pos
|
|
3020
|
+
for idx in range(start_pos, len(content)):
|
|
3021
|
+
c = content[idx]
|
|
3022
|
+
if c == "{":
|
|
3023
|
+
depth += 1
|
|
3024
|
+
elif c == "}":
|
|
3025
|
+
depth -= 1
|
|
3026
|
+
if depth == 0:
|
|
3027
|
+
end_pos = idx
|
|
3028
|
+
break
|
|
3029
|
+
body = content[start_pos + 1 : end_pos]
|
|
3030
|
+
line_no = content[: m.start()].count("\n") + 1
|
|
3031
|
+
|
|
3032
|
+
tbl_dialect = "postgres" if helper_fn == "pgTable" else ("mysql" if helper_fn == "mysqlTable" else "sqlite")
|
|
3033
|
+
tbl_cid = build_db_canonical_id(
|
|
3034
|
+
DatabaseEntityKind.TABLE,
|
|
3035
|
+
table=tbl_name,
|
|
3036
|
+
dialect=tbl_dialect,
|
|
3037
|
+
schema=schema,
|
|
3038
|
+
)
|
|
3039
|
+
model_cid = f"drizzle.{var_name}"
|
|
3040
|
+
|
|
3041
|
+
res.entities.append(
|
|
3042
|
+
DatabaseEntity(
|
|
3043
|
+
canonical_id=model_cid,
|
|
3044
|
+
kind=DatabaseEntityKind.ORM_MODEL.value,
|
|
3045
|
+
name=var_name,
|
|
3046
|
+
dialect=tbl_dialect,
|
|
3047
|
+
schema_name=schema,
|
|
3048
|
+
table_name=tbl_name,
|
|
3049
|
+
orm_model_id=model_cid,
|
|
3050
|
+
framework="drizzle",
|
|
3051
|
+
file_path=file_path,
|
|
3052
|
+
start_line=line_no,
|
|
3053
|
+
end_line=line_no,
|
|
3054
|
+
evidence=f"{var_name} = {helper_fn}('{tbl_name}')",
|
|
3055
|
+
evidence_class="FRAMEWORK_VERIFIED",
|
|
3056
|
+
)
|
|
3057
|
+
)
|
|
3058
|
+
res.entities.append(
|
|
3059
|
+
DatabaseEntity(
|
|
3060
|
+
canonical_id=tbl_cid,
|
|
3061
|
+
kind=DatabaseEntityKind.TABLE.value,
|
|
3062
|
+
name=tbl_name,
|
|
3063
|
+
dialect=tbl_dialect,
|
|
3064
|
+
schema_name=schema,
|
|
3065
|
+
table_name=tbl_name,
|
|
3066
|
+
orm_model_id=model_cid,
|
|
3067
|
+
framework="drizzle",
|
|
3068
|
+
file_path=file_path,
|
|
3069
|
+
start_line=line_no,
|
|
3070
|
+
end_line=line_no,
|
|
3071
|
+
evidence=f"{var_name} = {helper_fn}('{tbl_name}')",
|
|
3072
|
+
evidence_class="FRAMEWORK_VERIFIED",
|
|
3073
|
+
)
|
|
3074
|
+
)
|
|
3075
|
+
res.edges.append(
|
|
3076
|
+
RelationshipRecord(
|
|
3077
|
+
source=model_cid,
|
|
3078
|
+
target=tbl_cid,
|
|
3079
|
+
relationship="MAPS_TO_TABLE",
|
|
3080
|
+
evidence_class="FRAMEWORK_VERIFIED",
|
|
3081
|
+
confidence="HIGH",
|
|
3082
|
+
file=file_path,
|
|
3083
|
+
start_line=line_no,
|
|
3084
|
+
end_line=line_no,
|
|
3085
|
+
evidence=f"{var_name} -> {tbl_name}",
|
|
3086
|
+
status="FACT",
|
|
3087
|
+
parser_version=PARSER_VERSION,
|
|
3088
|
+
index_generation=index_generation,
|
|
3089
|
+
)
|
|
3090
|
+
)
|
|
3091
|
+
|
|
3092
|
+
# Parse column definitions supporting multi-line chained method calls
|
|
3093
|
+
col_entries = re.finditer(
|
|
3094
|
+
r"(?:^|\n)\s*([A-Za-z_$][\w$]*)\s*:\s*([A-Za-z_$][\w$]*)\s*\(([\s\S]*?)(?=(?:\n\s*[A-Za-z_$][\w$]*\s*:|\Z))",
|
|
3095
|
+
body,
|
|
3096
|
+
)
|
|
3097
|
+
for ce in col_entries:
|
|
3098
|
+
prop_key = ce.group(1)
|
|
3099
|
+
col_type = ce.group(2)
|
|
3100
|
+
rest = ce.group(3)
|
|
3101
|
+
|
|
3102
|
+
arg_m = re.match(r"^\s*['\"`]([^'\"`]+)['\"`]", rest)
|
|
3103
|
+
arg_col_name = arg_m.group(1) if arg_m else None
|
|
3104
|
+
col_name = (arg_col_name if arg_col_name else prop_key).lower()
|
|
3105
|
+
|
|
3106
|
+
is_pk = bool(".primaryKey(" in rest or prop_key == "id")
|
|
3107
|
+
is_unq = bool(".unique(" in rest)
|
|
3108
|
+
nullable = not bool(".notNull(" in rest or is_pk)
|
|
3109
|
+
|
|
3110
|
+
def_val = None
|
|
3111
|
+
if ".defaultNow(" in rest:
|
|
3112
|
+
def_val = "now()"
|
|
3113
|
+
elif ".default(" in rest:
|
|
3114
|
+
dm = re.search(r'\.default\(([^)]*)\)', rest)
|
|
3115
|
+
if dm:
|
|
3116
|
+
def_val = dm.group(1).strip()
|
|
3117
|
+
|
|
3118
|
+
tgt_tbl = None
|
|
3119
|
+
tgt_col = None
|
|
3120
|
+
if ".references(" in rest:
|
|
3121
|
+
ref_m = re.search(r'\.references\(\s*(?:\(\)\s*=>\s*)?([A-Za-z_$][\w$]*)\.([A-Za-z_$][\w$]*)', rest)
|
|
3122
|
+
if ref_m:
|
|
3123
|
+
tgt_tbl = ref_m.group(1).lower()
|
|
3124
|
+
tgt_col = ref_m.group(2).lower()
|
|
3125
|
+
|
|
3126
|
+
col_cid = build_db_canonical_id(
|
|
3127
|
+
DatabaseEntityKind.COLUMN,
|
|
3128
|
+
table=tbl_name,
|
|
3129
|
+
column=col_name,
|
|
3130
|
+
dialect=tbl_dialect,
|
|
3131
|
+
schema=schema,
|
|
3132
|
+
)
|
|
3133
|
+
res.entities.append(
|
|
3134
|
+
DatabaseEntity(
|
|
3135
|
+
canonical_id=col_cid,
|
|
3136
|
+
kind=DatabaseEntityKind.COLUMN.value,
|
|
3137
|
+
name=col_name,
|
|
3138
|
+
dialect=tbl_dialect,
|
|
3139
|
+
schema_name=schema,
|
|
3140
|
+
table_name=tbl_name,
|
|
3141
|
+
column_name=col_name,
|
|
3142
|
+
data_type=col_type,
|
|
3143
|
+
nullable=nullable,
|
|
3144
|
+
default_value=def_val,
|
|
3145
|
+
is_primary_key=is_pk,
|
|
3146
|
+
is_foreign_key=bool(tgt_tbl),
|
|
3147
|
+
is_unique=is_unq,
|
|
3148
|
+
target_table=tgt_tbl,
|
|
3149
|
+
target_column=tgt_col,
|
|
3150
|
+
orm_model_id=model_cid,
|
|
3151
|
+
framework="drizzle",
|
|
3152
|
+
file_path=file_path,
|
|
3153
|
+
start_line=line_no,
|
|
3154
|
+
end_line=line_no,
|
|
3155
|
+
evidence=f"{prop_key}: {col_type}",
|
|
3156
|
+
evidence_class="FRAMEWORK_VERIFIED",
|
|
3157
|
+
)
|
|
3158
|
+
)
|
|
3159
|
+
res.edges.append(
|
|
3160
|
+
RelationshipRecord(
|
|
3161
|
+
source=tbl_cid,
|
|
3162
|
+
target=col_cid,
|
|
3163
|
+
relationship="MAPS_TO_COLUMN",
|
|
3164
|
+
evidence_class="FRAMEWORK_VERIFIED",
|
|
3165
|
+
confidence="HIGH",
|
|
3166
|
+
file=file_path,
|
|
3167
|
+
start_line=line_no,
|
|
3168
|
+
end_line=line_no,
|
|
3169
|
+
evidence=f"{tbl_name}.{col_name}",
|
|
3170
|
+
status="FACT",
|
|
3171
|
+
parser_version=PARSER_VERSION,
|
|
3172
|
+
index_generation=index_generation,
|
|
3173
|
+
)
|
|
3174
|
+
)
|
|
3175
|
+
|
|
3176
|
+
|
|
3177
|
+
def _extract_mongoose_schema(
|
|
3178
|
+
content: str,
|
|
3179
|
+
file_path: str,
|
|
3180
|
+
module_name: str,
|
|
3181
|
+
index_generation: int,
|
|
3182
|
+
res: DatabaseExtractionResult,
|
|
3183
|
+
model_to_table: dict[str, str],
|
|
3184
|
+
) -> None:
|
|
3185
|
+
if "mongoose" not in content and "Schema" not in content and "model" not in content:
|
|
3186
|
+
return
|
|
3187
|
+
|
|
3188
|
+
res.detected_dialects.add("mongodb")
|
|
3189
|
+
|
|
3190
|
+
schema_blocks: dict[str, tuple[str, int, int]] = {}
|
|
3191
|
+
objects: dict[str, tuple[str, int, int]] = {}
|
|
3192
|
+
|
|
3193
|
+
# 1. Discover all top-level object declarations: const/let/var/export const <name>[: type] = { ... }
|
|
3194
|
+
for m in re.finditer(
|
|
3195
|
+
r"(?:(?:export\s+)?(?:const|let|var))\s+([A-Za-z_$][\w$]*)(?:\s*:\s*[^=;]+)?\s*=\s*\{",
|
|
3196
|
+
content,
|
|
3197
|
+
):
|
|
3198
|
+
name = m.group(1)
|
|
3199
|
+
start = m.end() - 1
|
|
3200
|
+
depth = 0
|
|
3201
|
+
end = start
|
|
3202
|
+
for idx in range(start, len(content)):
|
|
3203
|
+
if content[idx] == "{":
|
|
3204
|
+
depth += 1
|
|
3205
|
+
elif content[idx] == "}":
|
|
3206
|
+
depth -= 1
|
|
3207
|
+
if depth == 0:
|
|
3208
|
+
end = idx
|
|
3209
|
+
break
|
|
3210
|
+
start_ln = content.count("\n", 0, m.start()) + 1
|
|
3211
|
+
end_ln = content.count("\n", 0, end) + 1
|
|
3212
|
+
objects[name] = (content[start + 1 : end], start_ln, end_ln)
|
|
3213
|
+
|
|
3214
|
+
# 2. Discover all Schema declarations: const/let/var/export <name>[: type] = new (mongoose.)?Schema(...)
|
|
3215
|
+
for m in re.finditer(
|
|
3216
|
+
r"(?:(?:export\s+)?(?:const|let|var)\s+)?([A-Za-z_$][\w$]*)(?:\s*:\s*[^=;]+)?\s*=\s*new\s+(?:mongoose\.)?Schema\s*(?:<[^>]+>)?\s*\(\s*([A-Za-z_$][\w$]*|\{)?",
|
|
3217
|
+
content,
|
|
3218
|
+
):
|
|
3219
|
+
s_var = m.group(1)
|
|
3220
|
+
arg = m.group(2)
|
|
3221
|
+
if arg == "{":
|
|
3222
|
+
start_pos = m.end() - 1
|
|
3223
|
+
depth = 0
|
|
3224
|
+
end_pos = start_pos
|
|
3225
|
+
for idx in range(start_pos, len(content)):
|
|
3226
|
+
c = content[idx]
|
|
3227
|
+
if c == "{":
|
|
3228
|
+
depth += 1
|
|
3229
|
+
elif c == "}":
|
|
3230
|
+
depth -= 1
|
|
3231
|
+
if depth == 0:
|
|
3232
|
+
end_pos = idx
|
|
3233
|
+
break
|
|
3234
|
+
schema_body = content[start_pos + 1 : end_pos]
|
|
3235
|
+
start_ln = content.count("\n", 0, m.start()) + 1
|
|
3236
|
+
end_ln = content.count("\n", 0, end_pos) + 1
|
|
3237
|
+
schema_blocks[s_var] = (schema_body, start_ln, end_ln)
|
|
3238
|
+
elif arg and arg in objects:
|
|
3239
|
+
schema_blocks[s_var] = objects[arg]
|
|
3240
|
+
|
|
3241
|
+
for mm in re.finditer(
|
|
3242
|
+
r"(?:(?:(?:export\s+)?(?:const|let|var)\s+([A-Za-z_$][\w$]*)(?:\s*:\s*[^=;]+)?\s*=\s*)|(?:export\s+default\s+))?(?:mongoose\.)?model\s*(?:<[^>]+>)?\s*\(\s*['\"]([A-Za-z0-9_]+)['\"]\s*(?:,\s*([^\),\n]+))?",
|
|
3243
|
+
content,
|
|
3244
|
+
):
|
|
3245
|
+
var_name = mm.group(1)
|
|
3246
|
+
model_name = mm.group(2)
|
|
3247
|
+
raw_schema_arg = (mm.group(3) or "").strip()
|
|
3248
|
+
schema_var = raw_schema_arg
|
|
3249
|
+
|
|
3250
|
+
coll_name = _pluralize_table(_camel_to_snake(model_name)).lower()
|
|
3251
|
+
if var_name:
|
|
3252
|
+
model_to_table[var_name] = coll_name
|
|
3253
|
+
model_to_table[model_name] = coll_name
|
|
3254
|
+
model_to_table[model_name.lower()] = coll_name
|
|
3255
|
+
|
|
3256
|
+
m_line = content.count("\n", 0, mm.start()) + 1
|
|
3257
|
+
tbl_cid = f"db.UNKNOWN.UNKNOWN.{coll_name}"
|
|
3258
|
+
mdl_cid = f"{tbl_cid}.{model_name}"
|
|
3259
|
+
|
|
3260
|
+
res.entities.append(
|
|
3261
|
+
DatabaseEntity(
|
|
3262
|
+
kind=DatabaseEntityKind.TABLE,
|
|
3263
|
+
dialect="mongodb",
|
|
3264
|
+
schema_name="UNKNOWN",
|
|
3265
|
+
table_name=coll_name,
|
|
3266
|
+
canonical_id=tbl_cid,
|
|
3267
|
+
name=coll_name,
|
|
3268
|
+
file_path=file_path,
|
|
3269
|
+
start_line=m_line,
|
|
3270
|
+
end_line=m_line,
|
|
3271
|
+
confidence="HIGH",
|
|
3272
|
+
evidence=redact_secrets(mm.group(0)),
|
|
3273
|
+
evidence_class="FRAMEWORK_VERIFIED",
|
|
3274
|
+
status="FACT",
|
|
3275
|
+
)
|
|
3276
|
+
)
|
|
3277
|
+
|
|
3278
|
+
res.entities.append(
|
|
3279
|
+
DatabaseEntity(
|
|
3280
|
+
kind=DatabaseEntityKind.ORM_MODEL,
|
|
3281
|
+
dialect="mongodb",
|
|
3282
|
+
schema_name="UNKNOWN",
|
|
3283
|
+
table_name=coll_name,
|
|
3284
|
+
canonical_id=mdl_cid,
|
|
3285
|
+
name=model_name,
|
|
3286
|
+
target_table=coll_name,
|
|
3287
|
+
file_path=file_path,
|
|
3288
|
+
start_line=m_line,
|
|
3289
|
+
end_line=m_line,
|
|
3290
|
+
confidence="HIGH",
|
|
3291
|
+
evidence=redact_secrets(mm.group(0)),
|
|
3292
|
+
evidence_class="FRAMEWORK_VERIFIED",
|
|
3293
|
+
status="FACT",
|
|
3294
|
+
)
|
|
3295
|
+
)
|
|
3296
|
+
|
|
3297
|
+
res.edges.append(
|
|
3298
|
+
RelationshipRecord(
|
|
3299
|
+
source=mdl_cid,
|
|
3300
|
+
target=tbl_cid,
|
|
3301
|
+
relationship="MAPS_TO_COLLECTION",
|
|
3302
|
+
confidence="HIGH",
|
|
3303
|
+
evidence_class="FRAMEWORK_VERIFIED",
|
|
3304
|
+
status="FACT",
|
|
3305
|
+
file=file_path,
|
|
3306
|
+
start_line=m_line,
|
|
3307
|
+
end_line=m_line,
|
|
3308
|
+
evidence=redact_secrets(mm.group(0)),
|
|
3309
|
+
reason=f"mongoose_model_to_collection:{coll_name}",
|
|
3310
|
+
parser_version=PARSER_VERSION,
|
|
3311
|
+
index_generation=index_generation,
|
|
3312
|
+
)
|
|
3313
|
+
)
|
|
3314
|
+
|
|
3315
|
+
# Resolve body: schema_blocks, objects, or inline
|
|
3316
|
+
body_tuple: tuple[str, int, int] | None = None
|
|
3317
|
+
if schema_var in schema_blocks:
|
|
3318
|
+
body_tuple = schema_blocks[schema_var]
|
|
3319
|
+
elif schema_var in objects:
|
|
3320
|
+
body_tuple = objects[schema_var]
|
|
3321
|
+
elif "{" in content[mm.start() : mm.start() + 200]:
|
|
3322
|
+
# Inline schema: new Schema({ ... }) or { ... }
|
|
3323
|
+
inline_start = content.find("{", mm.start())
|
|
3324
|
+
if inline_start != -1 and inline_start < mm.start() + 200:
|
|
3325
|
+
depth = 0
|
|
3326
|
+
end_pos = inline_start
|
|
3327
|
+
for idx in range(inline_start, len(content)):
|
|
3328
|
+
c = content[idx]
|
|
3329
|
+
if c == "{":
|
|
3330
|
+
depth += 1
|
|
3331
|
+
elif c == "}":
|
|
3332
|
+
depth -= 1
|
|
3333
|
+
if depth == 0:
|
|
3334
|
+
end_pos = idx
|
|
3335
|
+
break
|
|
3336
|
+
body_tuple = (
|
|
3337
|
+
content[inline_start + 1 : end_pos],
|
|
3338
|
+
content.count("\n", 0, inline_start) + 1,
|
|
3339
|
+
content.count("\n", 0, end_pos) + 1,
|
|
3340
|
+
)
|
|
3341
|
+
|
|
3342
|
+
if body_tuple is not None:
|
|
3343
|
+
body, s_start, s_end = body_tuple
|
|
3344
|
+
for f_match in re.finditer(
|
|
3345
|
+
r"([A-Za-z_$][\w$]*)\s*:\s*(?:\{\s*([^}]+)\}|([A-Za-z0-9_$.]+))",
|
|
3346
|
+
body,
|
|
3347
|
+
):
|
|
3348
|
+
f_name = f_match.group(1)
|
|
3349
|
+
f_inner = f_match.group(2)
|
|
3350
|
+
f_direct = f_match.group(3)
|
|
3351
|
+
f_type = "Mixed"
|
|
3352
|
+
f_rest = ""
|
|
3353
|
+
if f_inner:
|
|
3354
|
+
type_m = re.search(r"type\s*:\s*([A-Za-z0-9_$.]+)", f_inner)
|
|
3355
|
+
f_type = type_m.group(1) if type_m else "Mixed"
|
|
3356
|
+
f_rest = f_inner
|
|
3357
|
+
elif f_direct:
|
|
3358
|
+
f_type = f_direct
|
|
3359
|
+
|
|
3360
|
+
col_cid = f"{tbl_cid}.{f_name}"
|
|
3361
|
+
is_req = bool(re.search(r"required\s*:\s*true", f_rest, re.IGNORECASE))
|
|
3362
|
+
is_uniq = bool(re.search(r"unique\s*:\s*true", f_rest, re.IGNORECASE))
|
|
3363
|
+
ref_match = re.search(r"ref\s*:\s*['\"]([A-Za-z0-9_]+)['\"]", f_rest)
|
|
3364
|
+
target_coll: str | None = None
|
|
3365
|
+
if ref_match:
|
|
3366
|
+
ref_model = ref_match.group(1)
|
|
3367
|
+
target_coll = _pluralize_table(_camel_to_snake(ref_model)).lower()
|
|
3368
|
+
|
|
3369
|
+
res.entities.append(
|
|
3370
|
+
DatabaseEntity(
|
|
3371
|
+
kind=DatabaseEntityKind.COLUMN,
|
|
3372
|
+
dialect="mongodb",
|
|
3373
|
+
schema_name="UNKNOWN",
|
|
3374
|
+
table_name=coll_name,
|
|
3375
|
+
column_name=f_name,
|
|
3376
|
+
data_type=f_type,
|
|
3377
|
+
nullable=bool(not is_req),
|
|
3378
|
+
is_primary_key=bool(f_name == "_id"),
|
|
3379
|
+
is_unique=bool(is_uniq),
|
|
3380
|
+
is_foreign_key=bool(target_coll is not None),
|
|
3381
|
+
target_table=target_coll,
|
|
3382
|
+
canonical_id=col_cid,
|
|
3383
|
+
name=f_name,
|
|
3384
|
+
file_path=file_path,
|
|
3385
|
+
start_line=s_start,
|
|
3386
|
+
end_line=s_end,
|
|
3387
|
+
confidence="HIGH",
|
|
3388
|
+
evidence=redact_secrets(f_match.group(0)),
|
|
3389
|
+
evidence_class="FRAMEWORK_VERIFIED",
|
|
3390
|
+
status="FACT",
|
|
3391
|
+
)
|
|
3392
|
+
)
|
|
3393
|
+
|
|
3394
|
+
res.edges.append(
|
|
3395
|
+
RelationshipRecord(
|
|
3396
|
+
source=tbl_cid,
|
|
3397
|
+
target=col_cid,
|
|
3398
|
+
relationship="MAPS_TO_COLUMN",
|
|
3399
|
+
confidence="HIGH",
|
|
3400
|
+
evidence_class="FRAMEWORK_VERIFIED",
|
|
3401
|
+
status="FACT",
|
|
3402
|
+
file=file_path,
|
|
3403
|
+
start_line=s_start,
|
|
3404
|
+
end_line=s_end,
|
|
3405
|
+
evidence=redact_secrets(f_match.group(0)),
|
|
3406
|
+
reason=f"mongoose_schema_field:{f_name}",
|
|
3407
|
+
parser_version=PARSER_VERSION,
|
|
3408
|
+
index_generation=index_generation,
|
|
3409
|
+
)
|
|
3410
|
+
)
|
|
3411
|
+
|
|
3412
|
+
if target_coll:
|
|
3413
|
+
res.edges.append(
|
|
3414
|
+
RelationshipRecord(
|
|
3415
|
+
source=col_cid,
|
|
3416
|
+
target=f"db.UNKNOWN.UNKNOWN.{target_coll}",
|
|
3417
|
+
relationship="FOREIGN_KEY_TO",
|
|
3418
|
+
confidence="HIGH",
|
|
3419
|
+
evidence_class="FRAMEWORK_VERIFIED",
|
|
3420
|
+
status="FACT",
|
|
3421
|
+
file=file_path,
|
|
3422
|
+
start_line=s_start,
|
|
3423
|
+
end_line=s_end,
|
|
3424
|
+
evidence=redact_secrets(f_match.group(0)),
|
|
3425
|
+
reason=f"mongoose_ref:{target_coll}",
|
|
3426
|
+
parser_version=PARSER_VERSION,
|
|
3427
|
+
index_generation=index_generation,
|
|
3428
|
+
)
|
|
3429
|
+
)
|
|
3430
|
+
|
|
2879
3431
|
|
|
2880
3432
|
def _extract_js_ts_database(
|
|
2881
3433
|
content: str,
|
|
@@ -2887,23 +3439,68 @@ def _extract_js_ts_database(
|
|
|
2887
3439
|
index_generation: int,
|
|
2888
3440
|
res: DatabaseExtractionResult,
|
|
2889
3441
|
) -> None:
|
|
2890
|
-
|
|
3442
|
+
_extract_drizzle_schema(
|
|
3443
|
+
content,
|
|
3444
|
+
file_path,
|
|
3445
|
+
module_name,
|
|
3446
|
+
dialect,
|
|
3447
|
+
schema,
|
|
3448
|
+
index_generation,
|
|
3449
|
+
res,
|
|
3450
|
+
)
|
|
3451
|
+
_extract_mongoose_schema(
|
|
3452
|
+
content,
|
|
3453
|
+
file_path,
|
|
3454
|
+
module_name,
|
|
3455
|
+
index_generation,
|
|
3456
|
+
res,
|
|
3457
|
+
model_to_table,
|
|
3458
|
+
)
|
|
3459
|
+
|
|
2891
3460
|
lines = content.splitlines()
|
|
3461
|
+
active_scope: str = module_name
|
|
3462
|
+
|
|
2892
3463
|
for line_no, raw_line in enumerate(lines, start=1):
|
|
2893
3464
|
ev = redact_secrets(raw_line.strip())
|
|
2894
|
-
|
|
3465
|
+
|
|
3466
|
+
fn_match = re.search(
|
|
3467
|
+
r"(?:async\s+)?function\s+([A-Za-z_$][\w$]*)|(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:async\s*)?\([^)]*\)\s*=>",
|
|
3468
|
+
raw_line,
|
|
3469
|
+
)
|
|
3470
|
+
if fn_match:
|
|
3471
|
+
fn_name = fn_match.group(1) or fn_match.group(2)
|
|
3472
|
+
if fn_name:
|
|
3473
|
+
active_scope = f"{module_name}.{fn_name}"
|
|
3474
|
+
else:
|
|
3475
|
+
meth_match = re.search(
|
|
3476
|
+
r"^\s*(?:public\s+|private\s+|protected\s+)?(?:async\s+)?([A-Za-z_$][\w$]*)\s*\([^)]*\)\s*(?::\s*[^{]+)?\{",
|
|
3477
|
+
raw_line,
|
|
3478
|
+
)
|
|
3479
|
+
if meth_match:
|
|
3480
|
+
m_name = meth_match.group(1)
|
|
3481
|
+
if m_name not in ("if", "for", "while", "switch", "catch", "constructor"):
|
|
3482
|
+
active_scope = f"{module_name}.{m_name}"
|
|
3483
|
+
|
|
3484
|
+
# 1. Prisma Client: (this.|ctx.|db.)?prisma.<model>.<action>(...)
|
|
2895
3485
|
for pm in re.finditer(
|
|
2896
|
-
r"\
|
|
3486
|
+
r"\b(?:this\.|ctx\.|db\.)?prisma\.([a-zA-Z_][a-zA-Z0-9_]*)\.(findMany|findUnique|findUniqueOrThrow|findFirst|findFirstOrThrow|count|aggregate|groupBy|create|createMany|createManyAndReturn|update|updateMany|upsert|delete|deleteMany)\b",
|
|
2897
3487
|
raw_line,
|
|
2898
3488
|
):
|
|
2899
3489
|
model_prop = pm.group(1)
|
|
2900
3490
|
method = pm.group(2)
|
|
2901
|
-
tbl =
|
|
2902
|
-
|
|
3491
|
+
tbl = model_to_table.get(
|
|
3492
|
+
model_prop,
|
|
3493
|
+
model_to_table.get(
|
|
3494
|
+
model_prop.lower(),
|
|
3495
|
+
_pluralize_table(_camel_to_snake(model_prop)),
|
|
3496
|
+
),
|
|
3497
|
+
).lower()
|
|
3498
|
+
|
|
3499
|
+
is_read = method.startswith(("find", "count", "aggregate", "groupBy"))
|
|
2903
3500
|
op = "SELECT" if is_read else ("INSERT" if "create" in method else ("DELETE" if "delete" in method else "UPDATE"))
|
|
2904
3501
|
rel = "READS_TABLE" if is_read else "WRITES_TABLE"
|
|
2905
3502
|
_emit_query_fact_and_edges(
|
|
2906
|
-
caller_id=
|
|
3503
|
+
caller_id=active_scope,
|
|
2907
3504
|
operation=op,
|
|
2908
3505
|
relationship=rel,
|
|
2909
3506
|
table_name=tbl,
|
|
@@ -2923,7 +3520,71 @@ def _extract_js_ts_database(
|
|
|
2923
3520
|
index_generation=index_generation,
|
|
2924
3521
|
res=res,
|
|
2925
3522
|
)
|
|
2926
|
-
|
|
3523
|
+
|
|
3524
|
+
# 2. Drizzle Query Builder: db.select().from(tableVar) / db.insert(tableVar) / db.update / db.delete
|
|
3525
|
+
for dm in re.finditer(
|
|
3526
|
+
r"\bdb(?:\.[a-zA-Z0-9_]+)?\.(select|insert|update|delete)\s*(?:\([^)]*\))?(?:\s*\.from\s*\(\s*([A-Za-z_$][\w$]*)\s*\)|\s*\(\s*([A-Za-z_$][\w$]*)\s*\))",
|
|
3527
|
+
raw_line,
|
|
3528
|
+
):
|
|
3529
|
+
action = dm.group(1)
|
|
3530
|
+
table_var = dm.group(2) or dm.group(3)
|
|
3531
|
+
if table_var:
|
|
3532
|
+
tbl = model_to_table.get(table_var, table_var).lower()
|
|
3533
|
+
is_read = (action == "select")
|
|
3534
|
+
op = "SELECT" if is_read else ("INSERT" if action == "insert" else ("DELETE" if action == "delete" else "UPDATE"))
|
|
3535
|
+
rel = "READS_TABLE" if is_read else "WRITES_TABLE"
|
|
3536
|
+
_emit_query_fact_and_edges(
|
|
3537
|
+
caller_id=active_scope,
|
|
3538
|
+
operation=op,
|
|
3539
|
+
relationship=rel,
|
|
3540
|
+
table_name=tbl,
|
|
3541
|
+
columns=(),
|
|
3542
|
+
read_columns=(),
|
|
3543
|
+
framework="drizzle",
|
|
3544
|
+
normalized_sql=f"{op} {tbl}",
|
|
3545
|
+
dialect=dialect,
|
|
3546
|
+
schema=schema,
|
|
3547
|
+
file_path=file_path,
|
|
3548
|
+
start_line=line_no,
|
|
3549
|
+
end_line=line_no,
|
|
3550
|
+
evidence=ev,
|
|
3551
|
+
confidence="HIGH",
|
|
3552
|
+
evidence_class="FRAMEWORK_VERIFIED",
|
|
3553
|
+
status="FACT",
|
|
3554
|
+
index_generation=index_generation,
|
|
3555
|
+
res=res,
|
|
3556
|
+
)
|
|
3557
|
+
|
|
3558
|
+
# 3. Drizzle Relational Queries: db.query.<tableVar>.(findMany|findFirst)(...)
|
|
3559
|
+
for dqm in re.finditer(
|
|
3560
|
+
r"\bdb(?:\.[a-zA-Z0-9_]+)?\.query\.([A-Za-z_$][\w$]*)\.(findMany|findFirst)\b",
|
|
3561
|
+
raw_line,
|
|
3562
|
+
):
|
|
3563
|
+
table_var = dqm.group(1)
|
|
3564
|
+
tbl = model_to_table.get(table_var, table_var).lower()
|
|
3565
|
+
_emit_query_fact_and_edges(
|
|
3566
|
+
caller_id=active_scope,
|
|
3567
|
+
operation="SELECT",
|
|
3568
|
+
relationship="READS_TABLE",
|
|
3569
|
+
table_name=tbl,
|
|
3570
|
+
columns=(),
|
|
3571
|
+
read_columns=(),
|
|
3572
|
+
framework="drizzle",
|
|
3573
|
+
normalized_sql=f"SELECT {tbl}",
|
|
3574
|
+
dialect=dialect,
|
|
3575
|
+
schema=schema,
|
|
3576
|
+
file_path=file_path,
|
|
3577
|
+
start_line=line_no,
|
|
3578
|
+
end_line=line_no,
|
|
3579
|
+
evidence=ev,
|
|
3580
|
+
confidence="HIGH",
|
|
3581
|
+
evidence_class="FRAMEWORK_VERIFIED",
|
|
3582
|
+
status="FACT",
|
|
3583
|
+
index_generation=index_generation,
|
|
3584
|
+
res=res,
|
|
3585
|
+
)
|
|
3586
|
+
|
|
3587
|
+
# 4. Knex: knex("users").select / insert / update / del
|
|
2927
3588
|
for km in re.finditer(
|
|
2928
3589
|
r'\bknex\(\s*["\']([a-zA-Z0-9_]+)["\']\s*\)\.(select|where|first|insert|update|del|delete)\b',
|
|
2929
3590
|
raw_line,
|
|
@@ -2934,7 +3595,7 @@ def _extract_js_ts_database(
|
|
|
2934
3595
|
op = "SELECT" if is_read else ("INSERT" if method == "insert" else ("DELETE" if method in ("del", "delete") else "UPDATE"))
|
|
2935
3596
|
rel = "READS_TABLE" if is_read else "WRITES_TABLE"
|
|
2936
3597
|
_emit_query_fact_and_edges(
|
|
2937
|
-
caller_id=
|
|
3598
|
+
caller_id=active_scope,
|
|
2938
3599
|
operation=op,
|
|
2939
3600
|
relationship=rel,
|
|
2940
3601
|
table_name=tbl,
|
|
@@ -2954,3 +3615,37 @@ def _extract_js_ts_database(
|
|
|
2954
3615
|
index_generation=index_generation,
|
|
2955
3616
|
res=res,
|
|
2956
3617
|
)
|
|
3618
|
+
|
|
3619
|
+
# 5. Mongoose Document Queries: Model.(find|findOne|findById|create|updateOne|deleteOne|...)
|
|
3620
|
+
for mgm in re.finditer(
|
|
3621
|
+
r"\b([A-Z][a-zA-Z0-9_]*)\.(find|findOne|findById|countDocuments|aggregate|create|insertOne|insertMany|updateOne|updateMany|deleteOne|deleteMany|findByIdAndUpdate|findByIdAndDelete)\b",
|
|
3622
|
+
raw_line,
|
|
3623
|
+
):
|
|
3624
|
+
model_ident = mgm.group(1)
|
|
3625
|
+
action = mgm.group(2)
|
|
3626
|
+
if model_ident in model_to_table or f"{model_ident.lower()}s" in model_to_table.values():
|
|
3627
|
+
tbl = model_to_table.get(model_ident, _pluralize_table(_camel_to_snake(model_ident))).lower()
|
|
3628
|
+
is_read = action.startswith(("find", "count", "aggregate"))
|
|
3629
|
+
op = "SELECT" if is_read else ("INSERT" if "create" in action or "insert" in action else ("DELETE" if "delete" in action else "UPDATE"))
|
|
3630
|
+
rel = "READS_COLLECTION" if is_read else "WRITES_COLLECTION"
|
|
3631
|
+
_emit_query_fact_and_edges(
|
|
3632
|
+
caller_id=active_scope,
|
|
3633
|
+
operation=op,
|
|
3634
|
+
relationship=rel,
|
|
3635
|
+
table_name=tbl,
|
|
3636
|
+
columns=(),
|
|
3637
|
+
read_columns=(),
|
|
3638
|
+
framework="mongoose",
|
|
3639
|
+
normalized_sql=f"{op} {tbl}",
|
|
3640
|
+
dialect="mongodb",
|
|
3641
|
+
schema="UNKNOWN",
|
|
3642
|
+
file_path=file_path,
|
|
3643
|
+
start_line=line_no,
|
|
3644
|
+
end_line=line_no,
|
|
3645
|
+
evidence=ev,
|
|
3646
|
+
confidence="HIGH",
|
|
3647
|
+
evidence_class="FRAMEWORK_VERIFIED",
|
|
3648
|
+
status="FACT",
|
|
3649
|
+
index_generation=index_generation,
|
|
3650
|
+
res=res,
|
|
3651
|
+
)
|