@tabularis/explain 0.3.0 → 0.3.1

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 (2) hide show
  1. package/dist/index.js +41 -3
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -726,7 +726,10 @@ function parsePostgresText(raw) {
726
726
  let planningTime = null;
727
727
  let executionTime = null;
728
728
  let hasAnalyzeData = false;
729
+ let hasBuffers = false;
729
730
  const ids = new NodeIdAllocator();
731
+ let rootIndent = null;
732
+ let inTrailer = false;
730
733
  const stack = [];
731
734
  let root = null;
732
735
  const promoteOrAttach = (node) => {
@@ -768,12 +771,16 @@ function parsePostgresText(raw) {
768
771
  if (node.actual_rows !== null || node.actual_time_ms !== null) {
769
772
  hasAnalyzeData = true;
770
773
  }
774
+ rootIndent ??= indent;
775
+ inTrailer = false;
771
776
  popUntilShallower(indent);
772
777
  stack.push({ indent, node });
773
778
  } else {
779
+ if (rootIndent !== null && indent <= rootIndent) inTrailer = true;
780
+ if (inTrailer) continue;
774
781
  const top = stack[stack.length - 1];
775
- if (top !== void 0) {
776
- applyTextAttribute(top.node, trimmed);
782
+ if (top !== void 0 && applyTextAttribute(top.node, trimmed)) {
783
+ hasBuffers = true;
777
784
  }
778
785
  }
779
786
  }
@@ -781,6 +788,7 @@ function parsePostgresText(raw) {
781
788
  if (root === null) {
782
789
  throw new Error("No plan nodes found in EXPLAIN text output");
783
790
  }
791
+ if (hasBuffers) fillMissingBuffers(root);
784
792
  return {
785
793
  root,
786
794
  planning_time_ms: planningTime,
@@ -884,7 +892,7 @@ function parseCostFields(inner) {
884
892
  function parseActualFields(inner) {
885
893
  const trimmed = inner.trim();
886
894
  if (trimmed.toLowerCase() === "never executed") {
887
- return [null, null, null];
895
+ return [0, 0, 0];
888
896
  }
889
897
  const rest = trimmed.startsWith("actual ") ? trimmed.slice(7) : trimmed;
890
898
  let totalTime = null;
@@ -911,6 +919,10 @@ function parseFiniteNumber(text) {
911
919
  return Number.isFinite(value) ? value : null;
912
920
  }
913
921
  function applyTextAttribute(node, content) {
922
+ if (content.startsWith("Buffers:")) {
923
+ applyBuffers(node, content.slice("Buffers:".length));
924
+ return true;
925
+ }
914
926
  if (content.startsWith("Filter:")) {
915
927
  node.filter = content.slice("Filter:".length).trim();
916
928
  } else if (content.startsWith("Index Cond:")) {
@@ -925,6 +937,32 @@ function applyTextAttribute(node, content) {
925
937
  node.extra[content.slice(0, sep).trim()] = content.slice(sep + 1).trim();
926
938
  }
927
939
  }
940
+ return false;
941
+ }
942
+ function applyBuffers(node, content) {
943
+ node.buffers_hit = 0;
944
+ node.buffers_read = 0;
945
+ for (const group of content.split(",")) {
946
+ const [scope, ...counters] = group.trim().split(/\s+/);
947
+ if (scope === void 0 || scope === "") continue;
948
+ const scopeLabel = scope.charAt(0).toUpperCase() + scope.slice(1).toLowerCase();
949
+ for (const counter of counters) {
950
+ const eq = counter.indexOf("=");
951
+ if (eq === -1) continue;
952
+ const name = counter.slice(0, eq).toLowerCase();
953
+ const value = parseFiniteNumber(counter.slice(eq + 1));
954
+ if (value === null) continue;
955
+ if (scopeLabel === "Shared" && name === "hit") node.buffers_hit = value;
956
+ else if (scopeLabel === "Shared" && name === "read") node.buffers_read = value;
957
+ else
958
+ node.extra[`${scopeLabel} ${name.charAt(0).toUpperCase()}${name.slice(1)} Blocks`] = value;
959
+ }
960
+ }
961
+ }
962
+ function fillMissingBuffers(node) {
963
+ node.buffers_hit ??= 0;
964
+ node.buffers_read ??= 0;
965
+ for (const child of node.children) fillMissingBuffers(child);
928
966
  }
929
967
 
930
968
  // src/parsers/sqlite.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tabularis/explain",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Parse, analyse and visualise a database EXPLAIN plan. Takes raw EXPLAIN output; never runs a query.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://github.com/TabularisDB/tabularis/tree/main/packages/explain",