Browse Source

Merge pull request #137 from sasha-alias/s-monorepo

moving sqldoc to sqltabs monorepo
dependabot/npm_and_yarn/src/sqldoc/mixin-deep-1.3.2
Sasha Aliashkevich 3 weeks ago
parent
commit
14e178339c
No account linked to committer's email address
11 changed files with 7278 additions and 1 deletions
  1. +1
    -1
      src/OutputConsole.js
  2. +19
    -0
      src/sqldoc/.eslintrc.yml
  3. +2
    -0
      src/sqldoc/.gitignore
  4. +21
    -0
      src/sqldoc/LICENSE.txt
  5. +3
    -0
      src/sqldoc/README.md
  6. +5318
    -0
      src/sqldoc/package-lock.json
  7. +30
    -0
      src/sqldoc/package.json
  8. +3
    -0
      src/sqldoc/sqldoc.js
  9. +1192
    -0
      src/sqldoc/src/SqlDoc.js
  10. +49
    -0
      src/sqldoc/src/datatypes.js
  11. +640
    -0
      src/sqldoc/src/pgplan.js

+ 1
- 1
src/OutputConsole.js View File

@@ -18,7 +18,7 @@
var React = require('react');
var ObjectInfo = require('./ObjectInfo');
var Actions = require('./Actions');
var SqlDoc = require('sqldoc');
var SqlDoc = require('./sqldoc/sqldoc');
var TabsStore = require('./TabsStore');

var OutputConsole = React.createClass({

+ 19
- 0
src/sqldoc/.eslintrc.yml View File

@@ -0,0 +1,19 @@
env:
browser: true
commonjs: true
es6: true
node: true
extends: 'eslint:recommended'
globals:
Atomics: readonly
SharedArrayBuffer: readonly
parser: babel-eslint
parserOptions:
ecmaFeatures:
jsx: true
ecmaVersion: 2018
plugins:
- react
rules:
"react/jsx-uses-vars": "error"
"no-console": 0

+ 2
- 0
src/sqldoc/.gitignore View File

@@ -0,0 +1,2 @@
build/.module-cache
node_modules

+ 21
- 0
src/sqldoc/LICENSE.txt View File

@@ -0,0 +1,21 @@
The MIT License (MIT)

Copyright (c) 2015 Aliaksandr Aliashkevich

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

+ 3
- 0
src/sqldoc/README.md View File

@@ -0,0 +1,3 @@
## SQL Documents renderer

Renderer of SQL documets used in [www.sqltabs.com](http://www.sqltabs.com)

+ 5318
- 0
src/sqldoc/package-lock.json
File diff suppressed because it is too large
View File


+ 30
- 0
src/sqldoc/package.json View File

@@ -0,0 +1,30 @@
{
"name": "sqldoc",
"version": "1.1.1",
"devDependencies": {
"babel": "^6.23.0",
"babel-cli": "^6.24.1",
"babel-eslint": "^10.0.1",
"babel-plugin-transform-react-jsx": "^6.24.1",
"babel-preset-es2015": "^6.24.1",
"eslint": "^5.16.0",
"eslint-plugin-react": "^7.13.0",
"mocha": "^2.2.5"
},
"dependencies": {
"crypto-js": "^3.1.9-1",
"c3": "^0.4.0",
"d3": "^3.5.0",
"jquery": "^3.3.1",
"marked": "^0.3.3",
"react": "^0.14.9",
"react-dom": "^0.14.9",
"topojson": "^2.2.0"
},
"main": "sqldoc.js",
"scripts": {
"test": "node_modules/.bin/mocha",
"build": "eslint src/ && babel --plugins transform-react-jsx --presets es2015 --out-dir=build src/",
"build:watch": "eslint src/ && babel --plugins transform-react-jsx --presets es2015 --out-dir=build src/ --watch"
}
}

+ 3
- 0
src/sqldoc/sqldoc.js View File

@@ -0,0 +1,3 @@
var SqlDoc = require('./build/SqlDoc');

module.exports = SqlDoc;

+ 1192
- 0
src/sqldoc/src/SqlDoc.js
File diff suppressed because it is too large
View File


+ 49
- 0
src/sqldoc/src/datatypes.js View File

@@ -0,0 +1,49 @@
var DataTypes = {
numericTypes: [
'number',
'numeric',
'bigint',
'smallint',
'int',
'int2',
'int4',
'int8',
'int16',
'int32',
'int64',
'float',
'float2',
'float4',
'float8',
'float16',
'float32',
'float64',
],

JSONTypes: [
'json',
'jsonb',
],

isNumeric: function(datatype){
if (typeof(datatype) != "undefined" && datatype != null){
return (this.numericTypes.indexOf(datatype.toLowerCase()) > -1);
} else {
return false;
}
},

isJSON: function(datatype){
if (typeof(datatype) != "undefined" && datatype != null){
return (this.JSONTypes.indexOf(datatype.toLowerCase()) > -1);
} else {
return false;
}
}
}

try{
module.exports = DataTypes;
} catch(e){
// skip
}

+ 640
- 0
src/sqldoc/src/pgplan.js View File

@@ -0,0 +1,640 @@
try {
var React = require('react');
var d3 = require('d3');
} catch(e) {
// pass
}

var Node = function(rn, text){
var self = this;
this.rn = rn;
this.text = text;
this.level = null;
this.parent_node = null;
this.kids = [];
this.cost = null;
this.exclusive_cost = null;
this.time = null;
this.loops = null;
this.inclusive_time = null;
this.exclusive_time = null;
this.subplan = false;
this.cte = false;
this.cte_id = null;
this.parent_cte = null;
this.never_executed = false;


// d3 related
this.name = rn;
this.value = 10;
this.parent = null;
//

this.parseParam = function(pattern){
var val = self.text.match(pattern);
if (val && val.length > 0){
val = val[1].split('..');
if (val && val.length == 2){
return val;
}
}
return null;
};

this.parseText = function(){
self.cost = self.parseParam(/cost=([^\s]*)/);
self.exclusive_cost = (self.cost ? self.cost[1] : null);
self.time = self.parseParam(/actual time=([^\s]*)/);

var loops = self.text.match(/loops=([0-9]*)/);
loops = loops ? loops[1] : null;
self.loops = loops;

self.inclusive_time = (self.time ? self.time[1]*self.loops : null);
self.exclusive_time = self.inclusive_time;

self.never_executed = (self.text.match(/\(never executed\)/) != null)

if (self.text.match(/\(+/) != null){ // if at least one bracket
self.node_description = self.text.match(/\s*[->]*(.*?)[(]/)[1];
self.node_details = self.text.match(/[(].*/)[0];
} else { // row without bracket is a details row
self.node_description = null;
self.node_details = self.text.match(/^[\s]*(.*)/)[1];
}
// collapsiblle nodes are started from "->", "InitPlan", "SubPlan"
self.collapsible = (self.text.match(/\s*->/) === null ? false : true);

if (self.text.match(/^[\s]*InitPlan/) != null || self.text.match(/^[\s]*SubPlan/) != null){
self.subplan = true;
self.collapsible = true;
self.node_description = self.text.trim();
self.node_details = null;
}

// detect CTE
if (self.text.match(/^[\s]*CTE/) != null && self.text.match(/^[\s]*CTE Scan/) === null){
self.cte = true;
self.collapsible = true;
self.subplan = true;
self.node_description = self.text.match(/^[\s]*(.*)/)[1];
self.node_details = null;
self.cte_id = self.text.match(/^[\s]*CTE ([^\s]*)/)[1];
}

// detect parent CTE
if (self.text.match(/\s*CTE Scan/) != null){
self.parent_cte = self.text.match(/\s*CTE Scan on ([^\s]*)/)[1];
}

// detect level
var level = self.text.match(/^[\s]*->/);
if (self.rn == 0){ // root node
self.node_level = 0;
} else if (level != null && level.length > 0){ // plan node
self.node_level = (level[0].length)-2;
} else { // details node
self.node_level = self.text.match(/^[\s]*/)[0].length;
}


};

this.setParent = function(parent_node){
self.parent_node = parent_node;

if (parent_node != null){
self.parent_node.addChild(self);
self.parent = parent_node.name;
} else {
self.parent = null;
}

if (parent_node != null && self.cost != null){
self.parent_node.deductCost(self.cost[1]);
}
if (parent_node != null && self.exclusive_time != null){
self.parent_node.deductTime(self.inclusive_time);
}
};

this.addChild = function(node){
self.kids.push(node);
};

this.deductCost = function(cost){
self.exclusive_cost = self.exclusive_cost - cost;
};

this.deductTime = function(time){
self.exclusive_time = self.exclusive_time - time;
};

this.hideSwitchChildren = function(is_hidden){
this.hidden = is_hidden;
if (this.kids){
this.kids.forEach(function(item){
item.hideSwitchChildren(is_hidden);
});
}
};

self.parseText();
return self;
}

var PGPlanNodes = function (records){

var nodes = [];
var ctes = [];
var subplans = [];

var getParent = function(node){
var ret = null;
nodes.forEach(function(item){
if (item.node_level < node.node_level){
ret = item;
}
});
return ret;
}

// find parent for each node and fill CTEs list
records.forEach(function(record, rn){
var node = new Node(rn, record[0]);
var parent_node = getParent(node);
node.setParent(parent_node);
nodes.push(node);
if (node.cte){
ctes.push(node);
}
if (node.subplan && !node.cte){
subplans.push(node);
}
});

var deductCte = function(deducted_node, subtrahend){
if (subtrahend.kids.length < 1){return;}
if (subtrahend.kids[0].cost){
deducted_node.deductCost(subtrahend.kids[0].cost[1]);
}
if (subtrahend.kids[0].inclusive_time){
deducted_node.deductTime(subtrahend.kids[0].inclusive_time);
}
}

// Deduct CTE and SubPlan cost/time
ctes.forEach(function(ctenode){

// CTE Scan includes the cost of CTE, so needs to be deducted
nodes.forEach(function(node){
if (node.parent_cte == ctenode.cte_id){ // cte scan node
deductCte(node, ctenode);
}
});

// if parent node of CTE is not CTE Scan, then deduct CTE cost from it
if (ctenode.parent_node.parent_cte == null){
deductCte(ctenode.parent_node, ctenode);
}
});

subplans.forEach(function(subplan_node){
if (subplan_node.parent_node != null){
deductCte(subplan_node.parent_node, subplan_node);
}
})


// replace records with nodes objects
nodes.forEach(function(node, idx){
records[idx] = node;
});

// calculate cost/time percentage for each node and parse the details about the nodes
var summary_record = records[0];
var total_cost = summary_record.cost ? summary_record.cost[1] : null;
var total_time = summary_record.inclusive_time;

records.forEach(function(record, idx){ // eslint-disable-line no-unused-vars
var exclusive_cost = record.exclusive_cost ? record.exclusive_cost : null;

var inclusive_cost_percentage = record.cost ? record.cost[1]/total_cost*100 : null;
var inclusive_time_percentage = record.time ? record.time[1]*record.loops/total_time*100 : null;
var cost_percentage = exclusive_cost ? exclusive_cost/total_cost*100 : null;
var time_percentage = record.exclusive_time ? record.exclusive_time/total_time*100 : null;

record.cost_percentage = cost_percentage;
record.inclusive_cost_percentage = inclusive_cost_percentage;
record.time_percentage = time_percentage;
record.inclusive_time_percentage = inclusive_time_percentage;
});

return records;

}

var PGPlan = React.createClass({

getInitialState: function(){
return {
data: this.props.nodes,
show_row_number: true,
view: 'plain',
subview: null,
highlight: true,
viewport_size: {
width: 960,
height: 500,
}
};
},

componentDidMount: function(){
if (this.state.view == "tree"){
this.mountTree();
}
},

componentDidUpdate: function(){
if (this.state.view == "tree"){
this.mountTree();
}
},

componentWillReceiveProps: function(newprops){
this.setState({data: newprops.nodes});
},

collapseSwitch: function(record_idx){
var data = this.state.data;

var is_collapsed =!data[record_idx].collapsed;

data[record_idx].collapsed = is_collapsed;

data[record_idx].kids.forEach(function(node){
data[node.rn].hideSwitchChildren(is_collapsed);
data[node.rn].collapsed = false;
});

this.setState({data: data});
},

switchRowNumber: function(){
this.setState({show_row_number: !this.state.show_row_number});
},

switchView: function(view, subview){
this.setState({view: view, subview: subview});
},

switchHighlight: function(){
this.setState({highlight: !this.state.highlight});
},

renderRecord: function(record, idx){

var self = this;

if (record.hidden){
return null;
}

var val = record[0];

var exclusive_percentage = record.cost_percentage;
var inclusive_percentage = record.inclusive_cost_percentage;
if (record.time_percentage != null){
exclusive_percentage = record.time_percentage;
inclusive_percentage = record.inclusive_time_percentage;
}

var exclusive_color = "rgba(251, 2, 2, 0.4)";
var inclusive_color = "rgba(251, 2, 2, 0.1)";
if (record.never_executed){
exclusive_color = "rgba(51, 122, 183, 0.4)";
inclusive_color = "rgba(51, 122, 183, 0.1)";
}

if (exclusive_percentage === null){exclusive_percentage = 0}
if (inclusive_percentage === null){inclusive_percentage = 0}

var exclusive_gradient = exclusive_color+", "+exclusive_color+" "+exclusive_percentage+"%, ";
var inclusive_gradient = inclusive_color+" "+exclusive_percentage+"%, "+inclusive_color+" "+inclusive_percentage+"%, ";
var transparent_gradient = "transparent "+inclusive_percentage+"%, transparent 100%";

var style = "linear-gradient(to right, "+exclusive_gradient+inclusive_gradient+transparent_gradient+")";

// wrap explain plan nodes with span tag
if (idx == 0 && this.state.highlight){ // 1st row is always a node
val = <span>
<span className="explain-plan-header-title">
{record.node_description}
</span>
<span className="explain-plan-details">
{record.node_details}
</span></span>
} else {
var spaces = <span>{" ".repeat(record.node_level)}</span>;

var record_style = "explain-plan-record";
var collapse = null;
var collapse_note = null;
if (record.collapsible && !record.subplan) {
record_style = "explain-plan-collapsible-record";

var collapse_icon = "glyphicon-circle-arrow-right";
if (record.collapsed){
collapse_icon = "glyphicon-circle-arrow-up";
collapse_note = "[subtree skipped]";
}

collapse = <span>
<span className={"glyphicon "+collapse_icon+" explain-plan-node-arrow"} onClick={
function(){self.collapseSwitch(idx);}
}></span>
<span className="explain-plan-node-arrow-hidden">-&gt;</span>
</span>;
}

if (record.cte) {
record_style = "explain-plan-cte-record";
}
///////

if (this.state.highlight){
val = <span>
{spaces}
{collapse}
<span className="explain-plan-node-title">
{record.node_description}
</span>
<span className="explain-plan-details">
{record.node_details}
</span>
<span className="explain-plan-node-skipped-note">{collapse_note}</span>
</span>
} else {
val = <span>{record.text}</span>
style = "";
}
}

var rn = null;
if (this.state.show_row_number){
rn = <td className="record-rownum">{idx+1}</td>
} else {
rn = <td className="record-rownum"></td>
}

return (<tr key={"plan-record-"+idx} className={record_style}>
{rn}
<td style={{background: style}}>{val}</td>
</tr>);
//<td> {record.inclusive_time} </td>
//<td> {record.exclusive_time} </td>
//<td> {record.time_percentage} % </td>
},

render: function(){
var self = this;

if (this.state.view == "tree"){
return this.renderTree();
}

var data = this.state.data;
var plan_records = [];

if (typeof(data) == "undefined"){
return null;
}

data.forEach(function(record, idx){
plan_records.push(
self.renderRecord(record, idx)
);
});
return (<div>
{self.renderViewSwitcher()}
<table className="table-resultset table table-hover">
<thead>
<tr>
<th><span className="explain-plan-rownum-switch" onClick={self.switchRowNumber}>#</span></th>
<th><span className="explain-plan-rownum-switch" onClick={self.switchHighlight}> QUERY PLAN </span></th>
</tr>
</thead>
<tbody>
{plan_records}
</tbody>
</table>
</div>)
},

renderViewSwitcher: function(){
var self = this;
var zoom = null;
if (self.state.view == "tree"){
zoom = <span>
<span className="glyphicon glyphicon-zoom-out explain-plan-view-switcher" onClick={self.zoomOut}></span>
<span className="glyphicon glyphicon-zoom-in explain-plan-view-switcher" onClick={self.zoomIn}></span>
</span>
}
return (
<div className="explain-plan-toolbar">
<span className="glyphicon glyphicon-th-list explain-plan-view-switcher" onClick={function(){self.switchView("plain")}}></span>
<span className="glyphicon glyphicon-tree-deciduous explain-plan-view-switcher" onClick={function(){self.switchView("tree")}}></span>
<span className="glyphicon glyphicon-tree-conifer explain-plan-view-switcher" onClick={function(){self.switchView("tree", "conifer")}}></span>
{zoom}
</div>
);
},

zoomIn: function(){
this.setState({viewport_size: {
width: this.state.viewport_size.width + 100,
height: this.state.viewport_size.height + 200,
}});
},

zoomOut: function(){
this.setState({viewport_size: {
width: this.state.viewport_size.width - 100,
height: this.state.viewport_size.height - 200,
}});
},

renderTree: function(){
var self = this;
return (
<div treetype={self.state.subview}>
{self.renderViewSwitcher()}
<div ref="treeMountPoint" className="explain-plan-mount-div" viewport_width={self.state.viewport_size.width}></div>
</div>
);
},

mountTree: function(){
var self = this;
var treeData = [this.state.data[0]];

// Generate the tree diagram
var margin = {top: 20, right: 120, bottom: 20, left: 220},
width = self.state.viewport_size.width - margin.right - margin.left,
height = self.state.viewport_size.height - margin.top - margin.bottom;

var i = 0;

var tree = d3.layout.cluster()

tree.size([height, width])
.children(function(d){ // filter out details records
var kids = [];
if (d.kids){
d.kids.forEach(function(item){
if (item.collapsible){
kids.push(item)
}
});
}
return kids;
});

var diagonal = d3.svg.diagonal()
.projection(function(d) { return [d.y, d.x]; });

d3.select(this.refs.treeMountPoint).select("svg").remove(); // remove proviously rendered svg
var svg = d3.select(this.refs.treeMountPoint).append("svg")
.attr("height", height)
.attr("width", width)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");


var root = treeData[0];
update(root);

function update() {

// Compute tree layout.
var nodes = tree.nodes(root),
links = tree.links(nodes);

// compute CTE links
var ctes = {};
var cte_links = [];
nodes.forEach(function(node){
if (node.cte){
ctes[node.cte_id] = node;
}
});
nodes.forEach(function(node){ // don't merge it to the above loop as CTE Scan can appear before CTE
if (node.parent_cte){
cte_links.push({
source: node,
target: ctes[node.parent_cte],
});
}
});

if (self.state.subview == "conifer"){
// Normalize for fixed-depth.
nodes.forEach(function(d) { d.y = d.depth * 180; });
}

// Declare the nodes…
var node = svg.selectAll("g.node")
.data(nodes, function(d) { return d.id || (d.id = ++i); });

// Enter the nodes.
var maxY = 0;
var maxX = 0;
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) {
maxX = (d.x > maxX ? d.x : maxX);
maxY = (d.y > maxY ? d.y : maxY);
return "translate(" + d.y + "," + d.x + ")";
});

nodeEnter.append("circle")
.attr("r", function(d) {
var ret = (d.time_percentage ? d.time_percentage*0.5 : d.cost_percentage*0.5);
if (ret < 3){
return 3;
} else {
return ret;
}
})
.attr("class", function(d){
if (d.never_executed){
return "explain-plan-tree-circle explain-plan-tree-circle-never-executed";
} else {
return "explain-plan-tree-circle";
}

});

nodeEnter.append("text")
.attr("x", function(d) {
return d.children || d._children ?
(d.value + 4) * -1 : d.value + 4 })
.attr("dy", function(d){
if (!d.parent || !d.children){
return ".35em"
} else {
return "1.5em"
}
})
.attr("class", "explain-plan-tree-node-text")
.attr("text-anchor", function(d) {
if (!d.parent){ return "end"; }
if (!d.children){ return "start"; }
return "middle";
})
.text(function(d) { return d.node_description; })

// Declare the links…
var link = svg.selectAll("path.link")
.data(links, function(d) { return d.target.id; });

// Enter the links.
link.enter().insert("path", "g")
.attr("class", "link explain-plan-tree-link")
.attr("d", diagonal);

// remove initial CTE links
d3.select(self.refs.treeMountPoint).selectAll("path.link").filter(function(d){
return d.target.cte;
}).remove();

// declare proper CTE links
var cte_link = svg.selectAll("path.link")
.data(cte_links, function(d) { return d.source.id; });

// Enter cte links.
cte_link.enter().insert("path", "g")
.attr("class", "link explain-plan-tree-link explain-plan-cte-link")
.attr("d", function(d){return diagonal(d)});

// Adjust viewport size according to the built tree
d3.select(self.refs.treeMountPoint).selectAll("svg")
.attr("width", maxY+1000)
.attr("height", maxX+100)

// Adjust parent div height to display entire svg
d3.select(self.refs.treeMountPoint).attr("style", "height: "+(maxX+100)+"px");
}
}
});

try{
module.exports = {
"PGPlan": PGPlan,
"PGPlanNodes": PGPlanNodes,
}
} catch(e){
// pass
}

Loading…
Cancel
Save