From 7fbf906efdfa3902d917986365dd0a1b9bb029a4 Mon Sep 17 00:00:00 2001 From: cdemmigs Date: Sat, 10 Feb 2024 13:45:34 -0500 Subject: [PATCH] Refactor Sql{} class Break up monolithic class into smaller bits. --- coverage/tests.lcov | 1761 +++++++++++++++++++++++-------------------- dist/gssql.js | 749 +++++++++--------- package-lock.json | 4 +- package.json | 2 +- src/Sql.js | 739 ++++++++++-------- src/Views.js | 6 +- 6 files changed, 1742 insertions(+), 1519 deletions(-) diff --git a/coverage/tests.lcov b/coverage/tests.lcov index 56b05c9..c2c4bf2 100644 --- a/coverage/tests.lcov +++ b/coverage/tests.lcov @@ -1,12 +1,12 @@ -------------------|---------|----------|---------|---------|--------------------------------------- File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s -------------------|---------|----------|---------|---------|--------------------------------------- -All files | 95.74 | 94.26 | 94.88 | 95.74 | +All files | 95.76 | 94.28 | 94.92 | 95.76 | JoinTables.js | 99.7 | 98.07 | 100 | 99.7 | 291,579 ScriptSettings.js | 90.72 | 75 | 88.88 | 90.72 | 51-52,81-83,88-90,100-103,135-136 Select2Object.js | 96.73 | 89.47 | 100 | 96.73 | 75-76,101-103 SimpleParser.js | 98.38 | 97.51 | 100 | 98.38 | ...14,182-187,436-439,613-618,920-921 - Sql.js | 98.61 | 92.68 | 100 | 98.61 | ...2-163,457-458,487-488,676-677,1014 + Sql.js | 98.69 | 92.89 | 100 | 98.69 | ...2-163,392-393,423-424,796-797,1083 SqlTest.js | 92.92 | 94.94 | 89.45 | 92.92 | ...4116,4849-4853,4864-4867,4870-4881 Table.js | 98.71 | 88.46 | 100 | 98.71 | 77-78,158-160,163-164 TableData.js | 83.58 | 68.08 | 83.33 | 83.58 | ...28,363-364,391-392,402-404,410-413 @@ -15,12 +15,12 @@ All files | 95.74 | 94.26 | 94.88 | 95.74 | -------------------|---------|----------|---------|---------|--------------------------------------- File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s -------------------|---------|----------|---------|---------|--------------------------------------- -All files | 95.74 | 94.26 | 94.88 | 95.74 | +All files | 95.76 | 94.28 | 94.92 | 95.76 | JoinTables.js | 99.7 | 98.07 | 100 | 99.7 | 291,579 ScriptSettings.js | 90.72 | 75 | 88.88 | 90.72 | 51-52,81-83,88-90,100-103,135-136 Select2Object.js | 96.73 | 89.47 | 100 | 96.73 | 75-76,101-103 SimpleParser.js | 98.38 | 97.51 | 100 | 98.38 | ...14,182-187,436-439,613-618,920-921 - Sql.js | 98.61 | 92.68 | 100 | 98.61 | ...2-163,457-458,487-488,676-677,1014 + Sql.js | 98.69 | 92.89 | 100 | 98.69 | ...2-163,392-393,423-424,796-797,1083 SqlTest.js | 92.92 | 94.94 | 89.45 | 92.92 | ...4116,4849-4853,4864-4867,4870-4881 Table.js | 98.71 | 88.46 | 100 | 98.71 | 77-78,158-160,163-164 TableData.js | 83.58 | 68.08 | 83.33 | 83.58 | ...28,363-364,391-392,402-404,410-413 @@ -3003,46 +3003,51 @@ FN:300,addBindNamedRangeParameter FN:312,setBindValues FN:321,clearBindParameters FN:357,execute -FN:384,loadSchema -FN:397,setTableAlias -FN:410,setTables -FN:419,getTables -FN:429,getTableAlias -FN:444,selectFromSubQuery -FN:470,selectJoinSubQuery -FN:502,getTableAliasFromJoin -FN:522,getTableAliasUnion -FN:549,getTableAliasWhereIn -FN:569,getTableAliasWhereTerms -FN:586,getReferencedTableNames -FN:600,getReferencedTableNamesFromAst -FN:623,extractAstTables -FN:637,getTableNamesFrom -FN:655,getTableNamesJoin -FN:674,isIterable -FN:687,getTableNamesUnion -FN:704,getTableNamesWhereIn -FN:721,getTableNamesWhereTerms -FN:734,getTableNamesWhereCondition -FN:755,getTableNamesCorrelatedSelect -FN:772,locateAstTableAlias -FN:798,select -FN:862,distinctField -FN:891,pivotField -FN:914,getUniquePivotData -FN:943,addCalculatedPivotFieldsToAst -FN:974,unionSets -FN:1027,removeDuplicateRows -FN:1049,intersectRows -FN:1071,exceptRows -FN:1101,BindData -FN:1108,clear -FN:1119,add -FN:1133,addList -FN:1144,get -FN:1152,getBindDataList -FNF:56 -FNH:56 +FN:379,selectFromSubQuery +FN:406,selectJoinSubQuery +FN:435,loadSchema +FN:447,setTables +FN:456,getTables +FN:465,getReferencedTableNames +FN:479,getReferencedTableNamesFromAst +FN:505,select +FN:558,errorCheckSelectAST +FN:573,distinctField +FN:602,pivotField +FN:625,getUniquePivotData +FN:654,addCalculatedPivotFieldsToAst +FN:684,addColumnTitles +FN:697,cleanUp +FN:716,setTableAlias +FN:731,getTableAlias +FN:750,getTableAliasFromJoin +FN:771,locateAstTableAlias +FN:794,isIterable +FN:809,getTableAliasUnion +FN:836,getTableAliasWhereIn +FN:856,getTableAliasWhereTerms +FN:875,extractAstTables +FN:889,getTableNamesFrom +FN:907,getTableNamesJoin +FN:926,getTableNamesUnion +FN:943,getTableNamesWhereIn +FN:960,getTableNamesWhereTerms +FN:973,getTableNamesWhereCondition +FN:994,getTableNamesCorrelatedSelect +FN:1012,SqlSets +FN:1024,isSqlSet +FN:1040,unionSets +FN:1096,removeDuplicateRows +FN:1118,intersectRows +FN:1140,exceptRows +FN:1170,BindData +FN:1177,clear +FN:1188,add +FN:1202,addList +FN:1213,get +FN:1221,getBindDataList +FNF:61 +FNH:61 FNDA:45,log FNDA:3,gsSQL FNDA:6,execute @@ -3061,34 +3066,39 @@ FNDA:12,addBindNamedRangeParameter FNDA:151,setBindValues FNDA:1,clearBindParameters FNDA:356,execute +FNDA:354,selectFromSubQuery +FNDA:354,selectJoinSubQuery FNDA:354,loadSchema -FNDA:354,setTableAlias FNDA:151,setTables -FNDA:199,getTables +FNDA:516,getTables +FNDA:14,getReferencedTableNames +FNDA:34,getReferencedTableNamesFromAst +FNDA:354,select +FNDA:354,errorCheckSelectAST +FNDA:352,distinctField +FNDA:352,pivotField +FNDA:6,getUniquePivotData +FNDA:6,addCalculatedPivotFieldsToAst +FNDA:327,addColumnTitles +FNDA:327,cleanUp +FNDA:354,setTableAlias FNDA:728,getTableAlias -FNDA:354,selectFromSubQuery -FNDA:354,selectJoinSubQuery FNDA:728,getTableAliasFromJoin +FNDA:1421,locateAstTableAlias +FNDA:711,isIterable FNDA:728,getTableAliasUnion FNDA:728,getTableAliasWhereIn FNDA:728,getTableAliasWhereTerms -FNDA:14,getReferencedTableNames -FNDA:34,getReferencedTableNamesFromAst FNDA:92,extractAstTables FNDA:92,getTableNamesFrom FNDA:92,getTableNamesJoin -FNDA:711,isIterable FNDA:92,getTableNamesUnion FNDA:92,getTableNamesWhereIn FNDA:92,getTableNamesWhereTerms FNDA:21,getTableNamesWhereCondition FNDA:92,getTableNamesCorrelatedSelect -FNDA:1421,locateAstTableAlias -FNDA:353,select -FNDA:352,distinctField -FNDA:352,pivotField -FNDA:6,getUniquePivotData -FNDA:6,addCalculatedPivotFieldsToAst +FNDA:328,SqlSets +FNDA:328,isSqlSet FNDA:328,unionSets FNDA:4,removeDuplicateRows FNDA:1,intersectRows @@ -3472,128 +3482,128 @@ DA:370,356 DA:371,356 DA:372,356 DA:373,356 -DA:374,1 +DA:374,356 DA:375,1 -DA:376,353 -DA:377,353 -DA:378,356 +DA:376,1 +DA:377,1 +DA:378,1 DA:379,1 -DA:380,1 -DA:381,1 -DA:382,1 -DA:383,1 -DA:384,1 -DA:385,354 -DA:386,354 -DA:387,538 -DA:388,538 -DA:389,538 -DA:390,354 -DA:391,1 -DA:392,1 -DA:393,1 -DA:394,1 -DA:395,1 -DA:396,1 -DA:397,1 -DA:398,354 -DA:399,354 -DA:400,538 -DA:401,538 -DA:402,538 -DA:403,538 -DA:404,354 +DA:380,354 +DA:381,14 +DA:382,14 +DA:383,14 +DA:384,14 +DA:385,14 +DA:386,14 +DA:387,14 +DA:388,14 +DA:389,14 +DA:390,14 +DA:391,14 +DA:392,0 +DA:393,0 +DA:394,14 +DA:395,14 +DA:396,14 +DA:397,354 +DA:398,1 +DA:399,1 +DA:400,1 +DA:401,1 +DA:402,1 +DA:403,1 +DA:404,1 DA:405,1 DA:406,1 -DA:407,1 -DA:408,1 -DA:409,1 -DA:410,1 -DA:411,151 -DA:412,151 -DA:413,151 +DA:407,354 +DA:408,354 +DA:409,41 +DA:410,354 +DA:411,63 +DA:412,1 +DA:413,1 DA:414,1 DA:415,1 DA:416,1 DA:417,1 DA:418,1 DA:419,1 -DA:420,199 -DA:421,199 +DA:420,1 +DA:421,1 DA:422,1 -DA:423,1 -DA:424,1 +DA:423,0 +DA:424,0 DA:425,1 DA:426,1 DA:427,1 -DA:428,1 -DA:429,1 -DA:430,728 -DA:431,728 -DA:432,728 -DA:433,728 -DA:434,728 -DA:435,728 -DA:436,728 -DA:437,728 -DA:438,728 -DA:439,728 -DA:440,1 -DA:441,1 +DA:428,63 +DA:429,354 +DA:430,1 +DA:431,1 +DA:432,1 +DA:433,1 +DA:434,1 +DA:435,1 +DA:436,354 +DA:437,354 +DA:438,538 +DA:439,538 +DA:440,538 +DA:441,354 DA:442,1 DA:443,1 DA:444,1 -DA:445,354 -DA:446,14 -DA:447,14 -DA:448,14 -DA:449,14 -DA:450,14 -DA:451,14 -DA:452,14 -DA:453,14 -DA:454,14 -DA:455,14 -DA:456,14 -DA:457,0 -DA:458,0 -DA:459,14 -DA:460,14 -DA:461,14 -DA:462,354 +DA:445,1 +DA:446,1 +DA:447,1 +DA:448,151 +DA:449,151 +DA:450,151 +DA:451,1 +DA:452,1 +DA:453,1 +DA:454,1 +DA:455,1 +DA:456,1 +DA:457,516 +DA:458,516 +DA:459,1 +DA:460,1 +DA:461,1 +DA:462,1 DA:463,1 DA:464,1 DA:465,1 -DA:466,1 -DA:467,1 -DA:468,1 +DA:466,14 +DA:467,14 +DA:468,14 DA:469,1 DA:470,1 -DA:471,354 -DA:472,354 -DA:473,41 -DA:474,354 -DA:475,63 +DA:471,1 +DA:472,1 +DA:473,1 +DA:474,1 +DA:475,1 DA:476,1 DA:477,1 DA:478,1 DA:479,1 -DA:480,1 -DA:481,1 -DA:482,1 -DA:483,1 -DA:484,1 -DA:485,1 -DA:486,1 -DA:487,0 -DA:488,0 -DA:489,1 -DA:490,1 -DA:491,1 -DA:492,63 -DA:493,354 -DA:494,1 -DA:495,1 +DA:480,34 +DA:481,34 +DA:482,34 +DA:483,34 +DA:484,34 +DA:485,34 +DA:486,34 +DA:487,34 +DA:488,34 +DA:489,50 +DA:490,50 +DA:491,50 +DA:492,50 +DA:493,34 +DA:494,34 +DA:495,34 DA:496,1 DA:497,1 DA:498,1 @@ -3601,267 +3611,267 @@ DA:499,1 DA:500,1 DA:501,1 DA:502,1 -DA:503,728 -DA:504,728 -DA:505,728 -DA:506,728 -DA:507,728 -DA:508,1421 -DA:509,1421 -DA:510,1421 -DA:511,728 -DA:512,728 -DA:513,728 -DA:514,1 -DA:515,1 -DA:516,1 -DA:517,1 -DA:518,1 -DA:519,1 -DA:520,1 -DA:521,1 -DA:522,1 -DA:523,728 -DA:524,728 -DA:525,728 -DA:526,728 -DA:527,728 -DA:528,2741 -DA:529,35 -DA:530,35 -DA:531,35 -DA:532,35 -DA:533,35 -DA:534,35 -DA:535,35 -DA:536,2741 -DA:537,2741 -DA:538,728 -DA:539,728 -DA:540,728 -DA:541,1 -DA:542,1 -DA:543,1 -DA:544,1 -DA:545,1 -DA:546,1 -DA:547,1 -DA:548,1 -DA:549,1 -DA:550,728 -DA:551,728 -DA:552,29 -DA:553,29 -DA:554,728 -DA:555,728 -DA:556,11 -DA:557,11 -DA:558,728 -DA:559,728 -DA:560,728 +DA:503,1 +DA:504,1 +DA:505,1 +DA:506,354 +DA:507,354 +DA:508,354 +DA:509,354 +DA:510,354 +DA:511,354 +DA:512,354 +DA:513,354 +DA:514,354 +DA:515,354 +DA:516,354 +DA:517,354 +DA:518,354 +DA:519,354 +DA:520,354 +DA:521,354 +DA:522,354 +DA:523,354 +DA:524,354 +DA:525,354 +DA:526,354 +DA:527,354 +DA:528,354 +DA:529,354 +DA:530,354 +DA:531,354 +DA:532,354 +DA:533,354 +DA:534,354 +DA:535,354 +DA:536,354 +DA:537,354 +DA:538,354 +DA:539,354 +DA:540,354 +DA:541,354 +DA:542,354 +DA:543,354 +DA:544,354 +DA:545,354 +DA:546,354 +DA:547,354 +DA:548,354 +DA:549,354 +DA:550,354 +DA:551,354 +DA:552,354 +DA:553,1 +DA:554,1 +DA:555,1 +DA:556,1 +DA:557,1 +DA:558,1 +DA:559,354 +DA:560,1 DA:561,1 -DA:562,1 -DA:563,1 +DA:562,353 +DA:563,354 DA:564,1 DA:565,1 -DA:566,1 +DA:566,354 DA:567,1 DA:568,1 DA:569,1 -DA:570,728 -DA:571,728 -DA:572,55 -DA:573,118 -DA:574,118 -DA:575,118 -DA:576,55 -DA:577,728 -DA:578,728 -DA:579,728 -DA:580,1 -DA:581,1 -DA:582,1 -DA:583,1 -DA:584,1 -DA:585,1 -DA:586,1 -DA:587,14 -DA:588,14 -DA:589,14 -DA:590,1 -DA:591,1 -DA:592,1 -DA:593,1 -DA:594,1 -DA:595,1 +DA:570,1 +DA:571,1 +DA:572,1 +DA:573,1 +DA:574,352 +DA:575,352 +DA:576,352 +DA:577,352 +DA:578,352 +DA:579,352 +DA:580,352 +DA:581,7 +DA:582,7 +DA:583,7 +DA:584,7 +DA:585,7 +DA:586,7 +DA:587,7 +DA:588,7 +DA:589,7 +DA:590,7 +DA:591,7 +DA:592,7 +DA:593,352 +DA:594,352 +DA:595,352 DA:596,1 DA:597,1 DA:598,1 DA:599,1 DA:600,1 -DA:601,34 -DA:602,34 -DA:603,34 -DA:604,34 -DA:605,34 -DA:606,34 -DA:607,34 -DA:608,34 -DA:609,34 -DA:610,50 -DA:611,50 -DA:612,50 -DA:613,50 -DA:614,34 -DA:615,34 -DA:616,34 -DA:617,1 -DA:618,1 +DA:601,1 +DA:602,1 +DA:603,352 +DA:604,352 +DA:605,7 +DA:606,7 +DA:607,7 +DA:608,345 +DA:609,345 +DA:610,345 +DA:611,6 +DA:612,6 +DA:613,6 +DA:614,6 +DA:615,6 +DA:616,6 +DA:617,6 +DA:618,352 DA:619,1 DA:620,1 DA:621,1 DA:622,1 DA:623,1 -DA:624,92 -DA:625,92 -DA:626,92 -DA:627,92 -DA:628,92 -DA:629,92 -DA:630,92 -DA:631,1 -DA:632,1 -DA:633,1 -DA:634,1 -DA:635,1 -DA:636,1 -DA:637,1 -DA:638,92 -DA:639,92 -DA:640,87 -DA:641,78 -DA:642,78 -DA:643,9 -DA:644,9 -DA:645,9 -DA:646,87 -DA:647,87 -DA:648,92 +DA:624,1 +DA:625,1 +DA:626,6 +DA:627,6 +DA:628,6 +DA:629,6 +DA:630,6 +DA:631,6 +DA:632,6 +DA:633,6 +DA:634,6 +DA:635,6 +DA:636,6 +DA:637,6 +DA:638,6 +DA:639,6 +DA:640,6 +DA:641,6 +DA:642,6 +DA:643,1 +DA:644,1 +DA:645,1 +DA:646,1 +DA:647,1 +DA:648,1 DA:649,1 DA:650,1 DA:651,1 DA:652,1 DA:653,1 DA:654,1 -DA:655,1 -DA:656,92 -DA:657,92 -DA:658,6 -DA:659,92 -DA:660,9 -DA:661,8 -DA:662,8 -DA:663,1 -DA:664,1 -DA:665,1 -DA:666,9 -DA:667,92 -DA:668,1 -DA:669,1 -DA:670,1 -DA:671,1 -DA:672,1 -DA:673,1 -DA:674,1 -DA:675,711 -DA:676,0 -DA:677,0 -DA:678,711 -DA:679,711 -DA:680,711 +DA:655,6 +DA:656,6 +DA:657,6 +DA:658,17 +DA:659,17 +DA:660,17 +DA:661,17 +DA:662,11 +DA:663,11 +DA:664,11 +DA:665,44 +DA:666,44 +DA:667,44 +DA:668,44 +DA:669,11 +DA:670,6 +DA:671,6 +DA:672,6 +DA:673,17 +DA:674,6 +DA:675,6 +DA:676,6 +DA:677,1 +DA:678,1 +DA:679,1 +DA:680,1 DA:681,1 DA:682,1 DA:683,1 DA:684,1 -DA:685,1 -DA:686,1 -DA:687,1 -DA:688,92 -DA:689,92 -DA:690,92 -DA:691,368 +DA:685,327 +DA:686,168 +DA:687,168 +DA:688,327 +DA:689,327 +DA:690,327 +DA:691,1 DA:692,1 DA:693,1 DA:694,1 DA:695,1 -DA:696,368 -DA:697,92 -DA:698,1 -DA:699,1 -DA:700,1 -DA:701,1 -DA:702,1 -DA:703,1 -DA:704,1 -DA:705,92 -DA:706,92 -DA:707,92 -DA:708,4 -DA:709,4 -DA:710,92 -DA:711,92 -DA:712,3 -DA:713,3 -DA:714,92 +DA:696,1 +DA:697,1 +DA:698,327 +DA:699,7 +DA:700,7 +DA:701,327 +DA:702,327 +DA:703,8 +DA:704,8 +DA:705,327 +DA:706,327 +DA:707,327 +DA:708,1 +DA:709,1 +DA:710,1 +DA:711,1 +DA:712,1 +DA:713,1 +DA:714,1 DA:715,1 DA:716,1 -DA:717,1 -DA:718,1 -DA:719,1 -DA:720,1 -DA:721,1 -DA:722,92 -DA:723,4 -DA:724,9 -DA:725,9 -DA:726,4 -DA:727,92 +DA:717,354 +DA:718,354 +DA:719,538 +DA:720,538 +DA:721,538 +DA:722,538 +DA:723,354 +DA:724,1 +DA:725,1 +DA:726,1 +DA:727,1 DA:728,1 DA:729,1 DA:730,1 DA:731,1 -DA:732,1 -DA:733,1 -DA:734,1 -DA:735,21 -DA:736,21 -DA:737,10 -DA:738,10 -DA:739,21 -DA:740,21 -DA:741,5 -DA:742,5 -DA:743,21 +DA:732,728 +DA:733,728 +DA:734,728 +DA:735,728 +DA:736,728 +DA:737,728 +DA:738,728 +DA:739,728 +DA:740,728 +DA:741,728 +DA:742,1 +DA:743,1 DA:744,1 -DA:745,2 -DA:746,2 +DA:745,1 +DA:746,1 DA:747,1 -DA:748,21 +DA:748,1 DA:749,1 DA:750,1 -DA:751,1 -DA:752,1 -DA:753,1 -DA:754,1 -DA:755,1 -DA:756,92 -DA:757,78 -DA:758,110 -DA:759,1 -DA:760,1 -DA:761,110 -DA:762,78 -DA:763,92 +DA:751,728 +DA:752,728 +DA:753,728 +DA:754,728 +DA:755,728 +DA:756,1421 +DA:757,1421 +DA:758,1421 +DA:759,728 +DA:760,728 +DA:761,728 +DA:762,1 +DA:763,1 DA:764,1 DA:765,1 DA:766,1 @@ -3870,171 +3880,171 @@ DA:768,1 DA:769,1 DA:770,1 DA:771,1 -DA:772,1 +DA:772,1421 DA:773,1421 -DA:774,1421 +DA:774,711 DA:775,711 -DA:776,711 -DA:777,1421 +DA:776,1421 +DA:777,101 DA:778,101 -DA:779,101 -DA:780,711 -DA:781,1421 -DA:782,798 +DA:779,711 +DA:780,1421 +DA:781,798 +DA:782,42 DA:783,42 -DA:784,42 -DA:785,798 +DA:784,798 +DA:785,669 DA:786,669 -DA:787,669 -DA:788,1421 +DA:787,1421 +DA:788,1 DA:789,1 DA:790,1 DA:791,1 DA:792,1 DA:793,1 DA:794,1 -DA:795,1 -DA:796,1 -DA:797,1 -DA:798,1 -DA:799,353 -DA:800,353 -DA:801,353 -DA:802,353 -DA:803,353 -DA:804,353 -DA:805,352 -DA:806,352 -DA:807,352 -DA:808,352 -DA:809,352 -DA:810,352 -DA:811,352 -DA:812,352 -DA:813,352 -DA:814,352 -DA:815,352 -DA:816,352 -DA:817,352 -DA:818,352 -DA:819,352 -DA:820,352 -DA:821,352 -DA:822,352 -DA:823,352 -DA:824,352 -DA:825,352 -DA:826,352 -DA:827,352 -DA:828,352 -DA:829,352 -DA:830,352 -DA:831,352 -DA:832,352 -DA:833,352 -DA:834,352 -DA:835,352 -DA:836,352 -DA:837,352 -DA:838,352 -DA:839,352 -DA:840,352 -DA:841,353 -DA:842,168 -DA:843,168 -DA:844,327 -DA:845,327 -DA:846,353 -DA:847,7 -DA:848,7 -DA:849,327 -DA:850,353 -DA:851,8 -DA:852,8 -DA:853,327 -DA:854,327 -DA:855,353 +DA:795,711 +DA:796,0 +DA:797,0 +DA:798,711 +DA:799,711 +DA:800,711 +DA:801,1 +DA:802,1 +DA:803,1 +DA:804,1 +DA:805,1 +DA:806,1 +DA:807,1 +DA:808,1 +DA:809,1 +DA:810,728 +DA:811,728 +DA:812,728 +DA:813,728 +DA:814,728 +DA:815,2741 +DA:816,35 +DA:817,35 +DA:818,35 +DA:819,35 +DA:820,35 +DA:821,35 +DA:822,35 +DA:823,2741 +DA:824,2741 +DA:825,728 +DA:826,728 +DA:827,728 +DA:828,1 +DA:829,1 +DA:830,1 +DA:831,1 +DA:832,1 +DA:833,1 +DA:834,1 +DA:835,1 +DA:836,1 +DA:837,728 +DA:838,728 +DA:839,29 +DA:840,29 +DA:841,728 +DA:842,728 +DA:843,11 +DA:844,11 +DA:845,728 +DA:846,728 +DA:847,728 +DA:848,1 +DA:849,1 +DA:850,1 +DA:851,1 +DA:852,1 +DA:853,1 +DA:854,1 +DA:855,1 DA:856,1 -DA:857,1 -DA:858,1 -DA:859,1 -DA:860,1 -DA:861,1 -DA:862,1 -DA:863,352 -DA:864,352 -DA:865,352 -DA:866,352 -DA:867,352 -DA:868,352 -DA:869,352 -DA:870,7 -DA:871,7 -DA:872,7 -DA:873,7 -DA:874,7 -DA:875,7 -DA:876,7 -DA:877,7 -DA:878,7 -DA:879,7 -DA:880,7 -DA:881,7 -DA:882,352 -DA:883,352 -DA:884,352 +DA:857,728 +DA:858,728 +DA:859,55 +DA:860,118 +DA:861,118 +DA:862,118 +DA:863,55 +DA:864,728 +DA:865,728 +DA:866,728 +DA:867,1 +DA:868,1 +DA:869,1 +DA:870,1 +DA:871,1 +DA:872,1 +DA:873,1 +DA:874,1 +DA:875,1 +DA:876,92 +DA:877,92 +DA:878,92 +DA:879,92 +DA:880,92 +DA:881,92 +DA:882,92 +DA:883,1 +DA:884,1 DA:885,1 DA:886,1 DA:887,1 DA:888,1 DA:889,1 -DA:890,1 -DA:891,1 -DA:892,352 -DA:893,352 -DA:894,7 -DA:895,7 -DA:896,7 -DA:897,345 -DA:898,345 -DA:899,345 -DA:900,6 -DA:901,6 -DA:902,6 -DA:903,6 -DA:904,6 -DA:905,6 -DA:906,6 -DA:907,352 -DA:908,1 -DA:909,1 -DA:910,1 -DA:911,1 -DA:912,1 -DA:913,1 -DA:914,1 -DA:915,6 -DA:916,6 -DA:917,6 -DA:918,6 -DA:919,6 -DA:920,6 -DA:921,6 -DA:922,6 -DA:923,6 -DA:924,6 -DA:925,6 -DA:926,6 -DA:927,6 -DA:928,6 -DA:929,6 -DA:930,6 -DA:931,6 +DA:890,92 +DA:891,92 +DA:892,87 +DA:893,78 +DA:894,78 +DA:895,9 +DA:896,9 +DA:897,9 +DA:898,87 +DA:899,87 +DA:900,92 +DA:901,1 +DA:902,1 +DA:903,1 +DA:904,1 +DA:905,1 +DA:906,1 +DA:907,1 +DA:908,92 +DA:909,92 +DA:910,6 +DA:911,92 +DA:912,9 +DA:913,8 +DA:914,8 +DA:915,1 +DA:916,1 +DA:917,1 +DA:918,9 +DA:919,92 +DA:920,1 +DA:921,1 +DA:922,1 +DA:923,1 +DA:924,1 +DA:925,1 +DA:926,1 +DA:927,92 +DA:928,92 +DA:929,92 +DA:930,368 +DA:931,1 DA:932,1 DA:933,1 DA:934,1 -DA:935,1 -DA:936,1 +DA:935,368 +DA:936,92 DA:937,1 DA:938,1 DA:939,1 @@ -4042,29 +4052,29 @@ DA:940,1 DA:941,1 DA:942,1 DA:943,1 -DA:944,6 -DA:945,6 -DA:946,6 -DA:947,17 -DA:948,17 -DA:949,17 -DA:950,17 -DA:951,11 -DA:952,11 -DA:953,11 -DA:954,44 -DA:955,44 -DA:956,44 -DA:957,44 -DA:958,11 -DA:959,6 -DA:960,6 -DA:961,6 -DA:962,17 -DA:963,6 -DA:964,6 -DA:965,6 -DA:966,1 +DA:944,92 +DA:945,92 +DA:946,92 +DA:947,4 +DA:948,4 +DA:949,92 +DA:950,92 +DA:951,3 +DA:952,3 +DA:953,92 +DA:954,1 +DA:955,1 +DA:956,1 +DA:957,1 +DA:958,1 +DA:959,1 +DA:960,1 +DA:961,92 +DA:962,4 +DA:963,9 +DA:964,9 +DA:965,4 +DA:966,92 DA:967,1 DA:968,1 DA:969,1 @@ -4072,145 +4082,145 @@ DA:970,1 DA:971,1 DA:972,1 DA:973,1 -DA:974,1 -DA:975,328 -DA:976,328 -DA:977,328 -DA:978,328 -DA:979,1309 -DA:980,1298 -DA:981,1298 -DA:982,11 -DA:983,11 -DA:984,11 -DA:985,11 -DA:986,11 -DA:987,11 -DA:988,11 -DA:989,11 -DA:990,11 -DA:991,10 -DA:992,10 -DA:993,11 -DA:994,4 -DA:995,4 -DA:996,4 -DA:997,11 -DA:998,11 -DA:999,4 -DA:1000,4 -DA:1001,4 -DA:1002,11 -DA:1003,11 +DA:974,21 +DA:975,21 +DA:976,10 +DA:977,10 +DA:978,21 +DA:979,21 +DA:980,5 +DA:981,5 +DA:982,21 +DA:983,1 +DA:984,2 +DA:985,2 +DA:986,1 +DA:987,21 +DA:988,1 +DA:989,1 +DA:990,1 +DA:991,1 +DA:992,1 +DA:993,1 +DA:994,1 +DA:995,92 +DA:996,78 +DA:997,110 +DA:998,1 +DA:999,1 +DA:1000,110 +DA:1001,78 +DA:1002,92 +DA:1003,1 DA:1004,1 DA:1005,1 DA:1006,1 -DA:1007,11 -DA:1008,11 +DA:1007,1 +DA:1008,1 DA:1009,1 DA:1010,1 DA:1011,1 -DA:1012,11 -DA:1013,11 -DA:1014,0 -DA:1015,11 -DA:1016,11 -DA:1017,10 -DA:1018,327 -DA:1019,327 -DA:1020,328 +DA:1012,1 +DA:1013,328 +DA:1014,328 +DA:1015,328 +DA:1016,328 +DA:1017,328 +DA:1018,1 +DA:1019,1 +DA:1020,1 DA:1021,1 DA:1022,1 DA:1023,1 DA:1024,1 -DA:1025,1 -DA:1026,1 -DA:1027,1 -DA:1028,4 -DA:1029,4 -DA:1030,4 -DA:1031,4 -DA:1032,35 -DA:1033,35 -DA:1034,35 -DA:1035,33 -DA:1036,33 -DA:1037,33 -DA:1038,35 -DA:1039,4 -DA:1040,4 -DA:1041,4 -DA:1042,1 -DA:1043,1 -DA:1044,1 -DA:1045,1 -DA:1046,1 -DA:1047,1 -DA:1048,1 -DA:1049,1 -DA:1050,1 -DA:1051,1 -DA:1052,1 -DA:1053,1 -DA:1054,10 -DA:1055,10 -DA:1056,1 -DA:1057,1 -DA:1058,5 -DA:1059,1 -DA:1060,1 -DA:1061,5 -DA:1062,1 -DA:1063,1 -DA:1064,1 -DA:1065,1 -DA:1066,1 -DA:1067,1 -DA:1068,1 -DA:1069,1 -DA:1070,1 -DA:1071,1 -DA:1072,1 +DA:1025,328 +DA:1026,1288 +DA:1027,11 +DA:1028,11 +DA:1029,1288 +DA:1030,317 +DA:1031,317 +DA:1032,328 +DA:1033,1 +DA:1034,1 +DA:1035,1 +DA:1036,1 +DA:1037,1 +DA:1038,1 +DA:1039,1 +DA:1040,1 +DA:1041,328 +DA:1042,317 +DA:1043,317 +DA:1044,11 +DA:1045,11 +DA:1046,11 +DA:1047,328 +DA:1048,41 +DA:1049,30 +DA:1050,30 +DA:1051,11 +DA:1052,11 +DA:1053,11 +DA:1054,11 +DA:1055,11 +DA:1056,11 +DA:1057,11 +DA:1058,11 +DA:1059,11 +DA:1060,10 +DA:1061,10 +DA:1062,11 +DA:1063,4 +DA:1064,4 +DA:1065,4 +DA:1066,11 +DA:1067,11 +DA:1068,4 +DA:1069,4 +DA:1070,4 +DA:1071,11 +DA:1072,11 DA:1073,1 DA:1074,1 -DA:1075,5 -DA:1076,5 -DA:1077,5 +DA:1075,1 +DA:1076,11 +DA:1077,11 DA:1078,1 DA:1079,1 DA:1080,1 -DA:1081,2 -DA:1082,2 -DA:1083,2 -DA:1084,2 -DA:1085,2 -DA:1086,1 -DA:1087,1 -DA:1088,1 -DA:1089,2 -DA:1090,2 +DA:1081,11 +DA:1082,11 +DA:1083,0 +DA:1084,11 +DA:1085,11 +DA:1086,10 +DA:1087,10 +DA:1088,10 +DA:1089,328 +DA:1090,1 DA:1091,1 DA:1092,1 DA:1093,1 DA:1094,1 DA:1095,1 DA:1096,1 -DA:1097,1 -DA:1098,1 -DA:1099,1 -DA:1100,1 -DA:1101,1 -DA:1102,470 -DA:1103,470 -DA:1104,1 -DA:1105,1 -DA:1106,1 -DA:1107,1 -DA:1108,1 -DA:1109,471 -DA:1110,471 -DA:1111,471 -DA:1112,471 +DA:1097,4 +DA:1098,4 +DA:1099,4 +DA:1100,4 +DA:1101,35 +DA:1102,35 +DA:1103,35 +DA:1104,33 +DA:1105,33 +DA:1106,33 +DA:1107,35 +DA:1108,4 +DA:1109,4 +DA:1110,4 +DA:1111,1 +DA:1112,1 DA:1113,1 DA:1114,1 DA:1115,1 @@ -4218,44 +4228,113 @@ DA:1116,1 DA:1117,1 DA:1118,1 DA:1119,1 -DA:1120,170 -DA:1121,170 -DA:1122,170 -DA:1123,170 -DA:1124,170 -DA:1125,170 -DA:1126,170 -DA:1127,170 +DA:1120,1 +DA:1121,1 +DA:1122,1 +DA:1123,10 +DA:1124,10 +DA:1125,1 +DA:1126,1 +DA:1127,5 DA:1128,1 DA:1129,1 -DA:1130,1 +DA:1130,5 DA:1131,1 DA:1132,1 DA:1133,1 -DA:1134,114 -DA:1135,3 -DA:1136,3 -DA:1137,114 +DA:1134,1 +DA:1135,1 +DA:1136,1 +DA:1137,1 DA:1138,1 DA:1139,1 DA:1140,1 DA:1141,1 DA:1142,1 DA:1143,1 -DA:1144,1 -DA:1145,167 -DA:1146,167 +DA:1144,5 +DA:1145,5 +DA:1146,5 DA:1147,1 DA:1148,1 DA:1149,1 -DA:1150,1 -DA:1151,1 -DA:1152,1 -DA:1153,296 -DA:1154,296 +DA:1150,2 +DA:1151,2 +DA:1152,2 +DA:1153,2 +DA:1154,2 DA:1155,1 -LF:1155 -LH:1139 +DA:1156,1 +DA:1157,1 +DA:1158,2 +DA:1159,2 +DA:1160,1 +DA:1161,1 +DA:1162,1 +DA:1163,1 +DA:1164,1 +DA:1165,1 +DA:1166,1 +DA:1167,1 +DA:1168,1 +DA:1169,1 +DA:1170,1 +DA:1171,470 +DA:1172,470 +DA:1173,1 +DA:1174,1 +DA:1175,1 +DA:1176,1 +DA:1177,1 +DA:1178,471 +DA:1179,471 +DA:1180,471 +DA:1181,471 +DA:1182,1 +DA:1183,1 +DA:1184,1 +DA:1185,1 +DA:1186,1 +DA:1187,1 +DA:1188,1 +DA:1189,170 +DA:1190,170 +DA:1191,170 +DA:1192,170 +DA:1193,170 +DA:1194,170 +DA:1195,170 +DA:1196,170 +DA:1197,1 +DA:1198,1 +DA:1199,1 +DA:1200,1 +DA:1201,1 +DA:1202,1 +DA:1203,114 +DA:1204,3 +DA:1205,3 +DA:1206,114 +DA:1207,1 +DA:1208,1 +DA:1209,1 +DA:1210,1 +DA:1211,1 +DA:1212,1 +DA:1213,1 +DA:1214,167 +DA:1215,167 +DA:1216,1 +DA:1217,1 +DA:1218,1 +DA:1219,1 +DA:1220,1 +DA:1221,1 +DA:1222,296 +DA:1223,296 +DA:1224,1 +LF:1224 +LH:1208 BRDA:1,0,0,1 BRDA:11,1,0,45 BRDA:26,2,0,3 @@ -4308,161 +4387,167 @@ BRDA:321,48,0,1 BRDA:357,49,0,356 BRDA:358,50,0,188 BRDA:358,51,0,168 -BRDA:373,52,0,1 -BRDA:376,53,0,353 -BRDA:384,54,0,354 -BRDA:386,55,0,538 -BRDA:397,56,0,354 -BRDA:399,57,0,538 -BRDA:410,58,0,151 -BRDA:419,59,0,199 -BRDA:429,60,0,728 -BRDA:444,61,0,354 -BRDA:445,62,0,353 -BRDA:445,63,0,14 -BRDA:456,64,0,0 -BRDA:470,65,0,354 -BRDA:472,66,0,313 -BRDA:473,67,0,41 -BRDA:474,68,0,63 -BRDA:475,69,0,1 -BRDA:486,70,0,0 -BRDA:493,71,0,41 -BRDA:502,72,0,728 -BRDA:507,73,0,2107 -BRDA:507,74,0,1421 -BRDA:522,75,0,728 -BRDA:527,76,0,3426 -BRDA:527,77,0,2741 -BRDA:528,78,0,35 -BRDA:533,79,0,1 -BRDA:549,80,0,728 -BRDA:551,81,0,685 -BRDA:551,82,0,326 -BRDA:551,83,0,29 -BRDA:555,84,0,685 -BRDA:555,85,0,11 -BRDA:569,86,0,728 -BRDA:571,87,0,683 -BRDA:571,88,0,326 -BRDA:571,89,0,55 -BRDA:572,90,0,118 -BRDA:574,91,0,115 -BRDA:586,92,0,14 -BRDA:600,93,0,34 -BRDA:609,94,0,50 -BRDA:623,95,0,92 -BRDA:637,96,0,92 -BRDA:639,97,0,87 -BRDA:640,98,0,78 -BRDA:641,99,0,0 -BRDA:643,100,0,9 -BRDA:655,101,0,92 -BRDA:657,102,0,86 -BRDA:658,103,0,6 -BRDA:659,104,0,9 -BRDA:660,105,0,8 -BRDA:661,106,0,0 -BRDA:663,107,0,1 -BRDA:667,108,0,6 -BRDA:674,109,0,711 -BRDA:675,110,0,0 -BRDA:687,111,0,92 -BRDA:690,112,0,368 -BRDA:691,113,0,1 -BRDA:704,114,0,92 -BRDA:707,115,0,46 -BRDA:707,116,0,4 -BRDA:711,117,0,3 -BRDA:721,118,0,92 -BRDA:722,119,0,46 -BRDA:722,120,0,4 -BRDA:723,121,0,9 -BRDA:734,122,0,21 -BRDA:735,123,0,20 -BRDA:735,124,0,1 -BRDA:736,125,0,10 -BRDA:739,126,0,17 -BRDA:739,127,0,4 -BRDA:740,128,0,5 -BRDA:743,129,0,1 -BRDA:744,130,0,2 -BRDA:755,131,0,92 -BRDA:756,132,0,78 -BRDA:757,133,0,110 -BRDA:758,134,0,3 -BRDA:758,135,0,1 -BRDA:772,136,0,1421 -BRDA:774,137,0,710 -BRDA:775,138,0,711 -BRDA:777,139,0,101 -BRDA:780,140,0,711 -BRDA:781,141,0,798 -BRDA:782,142,0,440 -BRDA:782,143,0,42 -BRDA:786,144,0,669 -BRDA:798,145,0,353 -BRDA:804,146,0,1 -BRDA:805,147,0,352 -BRDA:841,148,0,168 -BRDA:844,149,0,327 -BRDA:846,150,0,7 -BRDA:849,151,0,327 -BRDA:850,152,0,121 -BRDA:850,153,0,8 -BRDA:853,154,0,327 -BRDA:862,155,0,352 -BRDA:866,156,0,0 -BRDA:869,157,0,7 -BRDA:891,158,0,352 -BRDA:893,159,0,7 -BRDA:895,160,0,1 -BRDA:897,161,0,345 -BRDA:900,162,0,6 -BRDA:914,163,0,6 -BRDA:943,164,0,6 -BRDA:946,165,0,17 -BRDA:950,166,0,11 -BRDA:950,167,0,11 -BRDA:953,168,0,44 -BRDA:955,169,0,24 -BRDA:955,170,0,20 -BRDA:959,171,0,6 -BRDA:974,172,0,328 -BRDA:978,173,0,1309 -BRDA:979,174,0,1298 -BRDA:982,175,0,11 -BRDA:990,176,0,1 -BRDA:991,177,0,10 -BRDA:993,178,0,4 -BRDA:998,179,0,4 -BRDA:1003,180,0,1 -BRDA:1008,181,0,1 -BRDA:1013,182,0,0 -BRDA:1017,183,0,10 -BRDA:1018,184,0,327 -BRDA:1027,185,0,4 -BRDA:1031,186,0,35 -BRDA:1034,187,0,33 -BRDA:1049,188,0,1 -BRDA:1053,189,0,10 -BRDA:1057,190,0,5 -BRDA:1058,191,0,1 -BRDA:1071,192,0,1 -BRDA:1074,193,0,5 -BRDA:1080,194,0,2 -BRDA:1088,195,0,2 -BRDA:1087,196,0,1 -BRDA:1101,197,0,470 -BRDA:1108,198,0,471 -BRDA:1119,199,0,170 -BRDA:1133,200,0,114 -BRDA:1134,201,0,3 -BRDA:1144,202,0,167 -BRDA:1145,203,0,0 -BRDA:1152,204,0,296 -BRF:205 -BRH:190 +BRDA:379,52,0,354 +BRDA:380,53,0,353 +BRDA:380,54,0,14 +BRDA:391,55,0,0 +BRDA:406,56,0,354 +BRDA:408,57,0,313 +BRDA:409,58,0,41 +BRDA:410,59,0,63 +BRDA:411,60,0,1 +BRDA:422,61,0,0 +BRDA:429,62,0,41 +BRDA:435,63,0,354 +BRDA:437,64,0,538 +BRDA:447,65,0,151 +BRDA:456,66,0,516 +BRDA:465,67,0,14 +BRDA:479,68,0,34 +BRDA:488,69,0,50 +BRDA:505,70,0,354 +BRDA:558,71,0,354 +BRDA:559,72,0,1 +BRDA:562,73,0,353 +BRDA:563,74,0,1 +BRDA:573,75,0,352 +BRDA:577,76,0,0 +BRDA:580,77,0,7 +BRDA:602,78,0,352 +BRDA:604,79,0,7 +BRDA:606,80,0,1 +BRDA:608,81,0,345 +BRDA:611,82,0,6 +BRDA:625,83,0,6 +BRDA:654,84,0,6 +BRDA:657,85,0,17 +BRDA:661,86,0,11 +BRDA:661,87,0,11 +BRDA:664,88,0,44 +BRDA:666,89,0,24 +BRDA:666,90,0,20 +BRDA:670,91,0,6 +BRDA:684,92,0,327 +BRDA:685,93,0,168 +BRDA:697,94,0,327 +BRDA:698,95,0,7 +BRDA:702,96,0,121 +BRDA:702,97,0,8 +BRDA:716,98,0,354 +BRDA:718,99,0,538 +BRDA:731,100,0,728 +BRDA:750,101,0,728 +BRDA:755,102,0,2107 +BRDA:755,103,0,1421 +BRDA:771,104,0,1421 +BRDA:773,105,0,710 +BRDA:774,106,0,711 +BRDA:776,107,0,101 +BRDA:779,108,0,711 +BRDA:780,109,0,798 +BRDA:781,110,0,440 +BRDA:781,111,0,42 +BRDA:785,112,0,669 +BRDA:794,113,0,711 +BRDA:795,114,0,0 +BRDA:809,115,0,728 +BRDA:814,116,0,3426 +BRDA:814,117,0,2741 +BRDA:815,118,0,35 +BRDA:820,119,0,1 +BRDA:836,120,0,728 +BRDA:838,121,0,685 +BRDA:838,122,0,326 +BRDA:838,123,0,29 +BRDA:842,124,0,685 +BRDA:842,125,0,11 +BRDA:856,126,0,728 +BRDA:858,127,0,683 +BRDA:858,128,0,326 +BRDA:858,129,0,55 +BRDA:859,130,0,118 +BRDA:861,131,0,115 +BRDA:875,132,0,92 +BRDA:889,133,0,92 +BRDA:891,134,0,87 +BRDA:892,135,0,78 +BRDA:893,136,0,0 +BRDA:895,137,0,9 +BRDA:907,138,0,92 +BRDA:909,139,0,86 +BRDA:910,140,0,6 +BRDA:911,141,0,9 +BRDA:912,142,0,8 +BRDA:913,143,0,0 +BRDA:915,144,0,1 +BRDA:919,145,0,6 +BRDA:926,146,0,92 +BRDA:929,147,0,368 +BRDA:930,148,0,1 +BRDA:943,149,0,92 +BRDA:946,150,0,46 +BRDA:946,151,0,4 +BRDA:950,152,0,3 +BRDA:960,153,0,92 +BRDA:961,154,0,46 +BRDA:961,155,0,4 +BRDA:962,156,0,9 +BRDA:973,157,0,21 +BRDA:974,158,0,20 +BRDA:974,159,0,1 +BRDA:975,160,0,10 +BRDA:978,161,0,17 +BRDA:978,162,0,4 +BRDA:979,163,0,5 +BRDA:982,164,0,1 +BRDA:983,165,0,2 +BRDA:994,166,0,92 +BRDA:995,167,0,78 +BRDA:996,168,0,110 +BRDA:997,169,0,3 +BRDA:997,170,0,1 +BRDA:1012,171,0,328 +BRDA:1024,172,0,328 +BRDA:1025,173,0,1288 +BRDA:1026,174,0,11 +BRDA:1030,175,0,317 +BRDA:1040,176,0,328 +BRDA:1041,177,0,317 +BRDA:1044,178,0,11 +BRDA:1047,179,0,41 +BRDA:1048,180,0,30 +BRDA:1051,181,0,11 +BRDA:1059,182,0,1 +BRDA:1060,183,0,10 +BRDA:1062,184,0,4 +BRDA:1067,185,0,4 +BRDA:1072,186,0,1 +BRDA:1077,187,0,1 +BRDA:1082,188,0,0 +BRDA:1086,189,0,10 +BRDA:1087,190,0,10 +BRDA:1096,191,0,4 +BRDA:1100,192,0,35 +BRDA:1103,193,0,33 +BRDA:1118,194,0,1 +BRDA:1122,195,0,10 +BRDA:1126,196,0,5 +BRDA:1127,197,0,1 +BRDA:1140,198,0,1 +BRDA:1143,199,0,5 +BRDA:1149,200,0,2 +BRDA:1157,201,0,2 +BRDA:1156,202,0,1 +BRDA:1170,203,0,470 +BRDA:1177,204,0,471 +BRDA:1188,205,0,170 +BRDA:1202,206,0,114 +BRDA:1203,207,0,3 +BRDA:1213,208,0,167 +BRDA:1214,209,0,0 +BRDA:1221,210,0,296 +BRF:211 +BRH:196 end_of_record TN: SF:SqlTest.js diff --git a/dist/gssql.js b/dist/gssql.js index 0f6d918..0bc0714 100644 --- a/dist/gssql.js +++ b/dist/gssql.js @@ -353,39 +353,76 @@ class Sql { // updated to use the new table. this.selectJoinSubQuery(); - Sql.setTableAlias(this.tables, this.ast); + TableAlias.setTableAlias(this.tables, this.ast); Sql.loadSchema(this.tables); - if (typeof this.ast.SELECT === 'undefined') { - throw new Error("Only SELECT statements are supported."); - } - return this.select(this.ast); } /** - * Updates 'tables' with table column information. - * @param {Map} tables + * Modifies AST when FROM is a sub-query rather than a table name. */ - static loadSchema(tables) { - // @ts-ignore - for (const table of tables.keys()) { - const tableInfo = tables.get(table.toUpperCase()); - tableInfo.loadSchema(); + selectFromSubQuery() { + if (typeof this.ast.FROM !== 'undefined' && typeof this.ast.FROM.SELECT !== 'undefined') { + const data = new Sql() + .setTables(this.tables) + .enableColumnTitle(true) + .replaceColumnTableNameWith(this.ast.FROM.table) + .execute(this.ast.FROM); + + if (typeof this.ast.FROM.table !== 'undefined') { + this.addTableData(this.ast.FROM.table, data); + } + + if (this.ast.FROM.table === '') { + throw new Error("Every derived table must have its own alias"); + } + + this.ast.FROM.as = ''; } } + /** - * Updates 'tables' with associated table ALIAS name found in ast. + * Checks if the JOINed table is a sub-query. + * The sub-query is evaluated and assigned the alias name. + * The AST is adjusted to use the new JOIN TABLE. + * @returns {void} + */ + selectJoinSubQuery() { + if (typeof this.ast.JOIN === 'undefined') + return; + + for (const joinAst of this.ast.JOIN) { + if (typeof joinAst.table !== 'string') { + const data = new Sql() + .setTables(this.tables) + .enableColumnTitle(true) + .replaceColumnTableNameWith(joinAst.as) + .execute(joinAst.table); + + if (typeof joinAst.as !== 'undefined') { + this.addTableData(joinAst.as, data); + } + + if (joinAst.as === '') { + throw new Error("Every derived table must have its own alias"); + } + joinAst.table = joinAst.as; + joinAst.as = ''; + } + } + } + + /** + * Updates 'tables' with table column information. * @param {Map} tables - * @param {Object} ast */ - static setTableAlias(tables, ast) { + static loadSchema(tables) { // @ts-ignore for (const table of tables.keys()) { - const tableAlias = Sql.getTableAlias(table, ast); const tableInfo = tables.get(table.toUpperCase()); - tableInfo.setTableAlias(tableAlias); + tableInfo.loadSchema(); } } @@ -407,75 +444,286 @@ class Sql { } /** - * Find table alias name (if any) for input actual table name. - * @param {String} tableName - Actual table name. - * @param {Object} ast - Abstract Syntax Tree for SQL. - * @returns {String} - Table alias. Empty string if not found. - */ - static getTableAlias(tableName, ast) { - let tableAlias = ""; - const ucTableName = tableName.toUpperCase(); + * Create table definition array from select string. + * @param {String} statement - full sql select statement. + * @returns {String[][]} - table definition array. + */ + static getReferencedTableNames(statement) { + const ast = SqlParse.sql2ast(statement); + return this.getReferencedTableNamesFromAst(ast); + } - tableAlias = Sql.getTableAliasFromJoin(tableAlias, ucTableName, ast); - tableAlias = Sql.getTableAliasUnion(tableAlias, ucTableName, ast); - tableAlias = Sql.getTableAliasWhereIn(tableAlias, ucTableName, ast); - tableAlias = Sql.getTableAliasWhereTerms(tableAlias, ucTableName, ast); + /** + * Create table definition array from select AST. + * @param {Object} ast - AST for SELECT. + * @returns {any[]} - table definition array. + * * [0] - table name. + * * [1] - sheet tab name + * * [2] - cache seconds + * * [3] - output column title flag + */ + static getReferencedTableNamesFromAst(ast) { + const DEFAULT_CACHE_SECONDS = 60; + const DEFAULT_COLUMNS_OUTPUT = true; + const tableSet = new Map(); - return tableAlias; + TableExtract.extractAstTables(ast, tableSet); + + const tableList = []; + // @ts-ignore + for (const key of tableSet.keys()) { + const tableDef = [key, key, DEFAULT_CACHE_SECONDS, DEFAULT_COLUMNS_OUTPUT]; + + tableList.push(tableDef); + } + + return tableList; } /** - * Modifies AST when FROM is a sub-query rather than a table name. + * Load SELECT data and return in double array. + * @param {Object} selectAst - Abstract Syntax Tree of SELECT + * @returns {any[][]} - double array useable by Google Sheet in custom function return value. + * * First row of data will be column name if column title output was requested. + * * First Array Index - ROW + * * Second Array Index - COLUMN */ - selectFromSubQuery() { - if (typeof this.ast.FROM !== 'undefined' && typeof this.ast.FROM.SELECT !== 'undefined') { - const data = new Sql() - .setTables(this.tables) - .enableColumnTitle(true) - .replaceColumnTableNameWith(this.ast.FROM.table) - .execute(this.ast.FROM); + select(selectAst) { + let ast = selectAst; - if (typeof this.ast.FROM.table !== 'undefined') { - this.addTableData(this.ast.FROM.table, data); - } + Sql.errorCheckSelectAST(ast); - if (this.ast.FROM.table === '') { - throw new Error("Every derived table must have its own alias"); - } + // Manipulate AST to add GROUP BY if DISTINCT keyword. + ast = Sql.distinctField(ast); - this.ast.FROM.as = ''; + // Manipulate AST add pivot fields. + ast = this.pivotField(ast); + + const view = new SelectTables(ast, this.tables, this.bindData); + + // JOIN tables to create a derived table. + view.join(ast); // skipcq: JS-D008 + + view.updateSelectedFields(ast); + + // Get the record ID's of all records matching WHERE condition. + const recordIDs = view.whereCondition(ast); + + // Get selected data records. + let viewTableData = view.getViewData(recordIDs); + + // Compress the data. + viewTableData = view.groupBy(ast, viewTableData); + + // Sort our selected data. + view.orderBy(ast, viewTableData); + + // Remove fields referenced but not included in SELECT field list. + view.removeTempColumns(viewTableData); + + // Limit rows returned. + viewTableData = SelectTables.limit(ast, viewTableData); + + // Apply SET rules for various union types. + const sqlSet = new SqlSets(ast, this.bindData, this.getTables()); + viewTableData = sqlSet.unionSets(viewTableData); + + // Add column titles + viewTableData = this.addColumnTitles(viewTableData, view); + + // Deal with empty dataset. + viewTableData = this.cleanUp(viewTableData); + + return viewTableData; + } + + /** + * Basic sanity check of AST for a SELECT statement. + * @param {object} ast + */ + static errorCheckSelectAST(ast) { + if (typeof ast.SELECT === 'undefined') { + throw new Error("Only SELECT statements are supported."); + } + + if (typeof ast.FROM === 'undefined') { + throw new Error("Missing keyword FROM"); } } /** - * Checks if the JOINed table is a sub-query. - * The sub-query is evaluated and assigned the alias name. - * The AST is adjusted to use the new JOIN TABLE. - * @returns {void} + * If 'GROUP BY' is not set and 'DISTINCT' column is specified, update AST to add 'GROUP BY'. + * @param {Object} ast - Abstract Syntax Tree for SELECT. + * @returns {Object} - Updated AST to include GROUP BY when DISTINCT field used. */ - selectJoinSubQuery() { - if (typeof this.ast.JOIN === 'undefined') - return; + static distinctField(ast) { + const astFields = ast.SELECT; - for (const joinAst of this.ast.JOIN) { - if (typeof joinAst.table !== 'string') { - const data = new Sql() - .setTables(this.tables) - .enableColumnTitle(true) - .replaceColumnTableNameWith(joinAst.as) - .execute(joinAst.table); + if (astFields.length === 0) + return ast; - if (typeof joinAst.as !== 'undefined') { - this.addTableData(joinAst.as, data); + const firstField = astFields[0].name.toUpperCase(); + if (firstField.startsWith("DISTINCT")) { + astFields[0].name = firstField.replace("DISTINCT", "").trim(); + + if (typeof ast['GROUP BY'] === 'undefined') { + const groupBy = []; + + for (const astItem of astFields) { + groupBy.push({ name: astItem.name, as: '' }); } - if (joinAst.as === '') { - throw new Error("Every derived table must have its own alias"); + ast["GROUP BY"] = groupBy; + } + } + + return ast; + } + + /** + * Add new column to AST for every AGGREGATE function and unique pivot column data. + * @param {Object} ast - AST which is checked to see if a PIVOT is used. + * @returns {Object} - Updated AST containing SELECT FIELDS for the pivot data OR original AST if no pivot. + */ + pivotField(ast) { + // If we are doing a PIVOT, it then requires a GROUP BY. + if (typeof ast.PIVOT !== 'undefined') { + if (typeof ast['GROUP BY'] === 'undefined') + throw new Error("PIVOT requires GROUP BY"); + } + else { + return ast; + } + + // These are all of the unique PIVOT field data points. + const pivotFieldData = this.getUniquePivotData(ast); + + ast.SELECT = Sql.addCalculatedPivotFieldsToAst(ast, pivotFieldData); + + return ast; + } + + /** + * Find distinct pivot column data. + * @param {Object} ast - Abstract Syntax Tree containing the PIVOT option. + * @returns {any[][]} - All unique data points found in the PIVOT field for the given SELECT. + */ + getUniquePivotData(ast) { + const pivotAST = {}; + + pivotAST.SELECT = ast.PIVOT; + pivotAST.SELECT[0].name = `DISTINCT ${pivotAST.SELECT[0].name}`; + pivotAST.FROM = ast.FROM; + pivotAST.WHERE = ast.WHERE; + + const pivotSql = new Sql() + .enableColumnTitle(false) + .setBindValues(this.bindData) + .copyTableData(this.getTables()); + + // These are all of the unique PIVOT field data points. + const tableData = pivotSql.execute(pivotAST); + + return tableData; + } + + /** + * Add new calculated fields to the existing SELECT fields. A field is add for each combination of + * aggregate function and unqiue pivot data points. The CASE function is used for each new field. + * A test is made if the column data equal the pivot data. If it is, the aggregate function data + * is returned, otherwise null. The GROUP BY is later applied and the appropiate pivot data will + * be calculated. + * @param {Object} ast - AST to be updated. + * @param {any[][]} pivotFieldData - Table data with unique pivot field data points. + * @returns {Object} - Abstract Sytax Tree with new SELECT fields with a CASE for each pivot data and aggregate function. + */ + static addCalculatedPivotFieldsToAst(ast, pivotFieldData) { + const newPivotAstFields = []; + + for (const selectField of ast.SELECT) { + // If this is an aggregrate function, we will add one for every pivotFieldData item + const functionNameRegex = /^\w+\s*(?=\()/; + const matches = selectField.name.match(functionNameRegex) + if (matches !== null && matches.length > 0) { + const args = SelectTables.parseForFunctions(selectField.name, matches[0].trim()); + + for (const fld of pivotFieldData) { + const caseTxt = `${matches[0]}(CASE WHEN ${ast.PIVOT[0].name} = '${fld}' THEN ${args[1]} ELSE 'null' END)`; + const asField = `${fld[0]} ${typeof selectField.as !== 'undefined' && selectField.as !== "" ? selectField.as : selectField.name}`; + newPivotAstFields.push({ name: caseTxt, as: asField }); } - joinAst.table = joinAst.as; - joinAst.as = ''; + } + else { + newPivotAstFields.push(selectField); } } + + return newPivotAstFields; + } + + /** + * Add column titles to data if needed. + * @param {any[][]} viewTableData + * @param {SelectTables} view + * @returns {any[][]} + */ + addColumnTitles(viewTableData, view) { + if (this.columnTitle) { + viewTableData.unshift(view.getColumnTitles(this.columnTableNameReplacement)); + } + + return viewTableData; + } + + /** + * If no data and no titles, create empty double array so sheets function does not have an error. + * @param {any[][]} viewTableData + * @returns {any[][]} + */ + cleanUp(viewTableData) { + if (viewTableData.length === 0) { + viewTableData.push([""]); + } + + if (viewTableData.length === 1 && viewTableData[0].length === 0) { + viewTableData[0] = [""]; + } + + return viewTableData; + } +} + +class TableAlias { + /** + * Updates 'tables' with associated table ALIAS name found in ast. + * @param {Map} tables + * @param {Object} ast + */ + static setTableAlias(tables, ast) { + // @ts-ignore + for (const table of tables.keys()) { + const tableAlias = TableAlias.getTableAlias(table, ast); + const tableInfo = tables.get(table.toUpperCase()); + tableInfo.setTableAlias(tableAlias); + } + } + + /** + * Find table alias name (if any) for input actual table name. + * @param {String} tableName - Actual table name. + * @param {Object} ast - Abstract Syntax Tree for SQL. + * @returns {String} - Table alias. Empty string if not found. + */ + static getTableAlias(tableName, ast) { + let tableAlias = ""; + const ucTableName = tableName.toUpperCase(); + + tableAlias = TableAlias.getTableAliasFromJoin(tableAlias, ucTableName, ast); + tableAlias = TableAlias.getTableAliasUnion(tableAlias, ucTableName, ast); + tableAlias = TableAlias.getTableAliasWhereIn(tableAlias, ucTableName, ast); + tableAlias = TableAlias.getTableAliasWhereTerms(tableAlias, ucTableName, ast); + + return tableAlias; } /** @@ -485,17 +733,56 @@ class Sql { * @param {Object} ast - Abstract Syntax Tree to search * @returns {String} - Table alias name. */ - static getTableAliasFromJoin(tableAlias, tableName, ast) { - const astTableBlocks = ['FROM', 'JOIN']; - let aliasNameFound = tableAlias; - - let i = 0; - while (aliasNameFound === "" && i < astTableBlocks.length) { - aliasNameFound = Sql.locateAstTableAlias(tableName, ast, astTableBlocks[i]); - i++; + static getTableAliasFromJoin(tableAlias, tableName, ast) { + const astTableBlocks = ['FROM', 'JOIN']; + let aliasNameFound = tableAlias; + + let i = 0; + while (aliasNameFound === "" && i < astTableBlocks.length) { + aliasNameFound = TableAlias.locateAstTableAlias(tableName, ast, astTableBlocks[i]); + i++; + } + + return aliasNameFound; + } + + + /** + * Search a property of AST for table alias name. + * @param {String} tableName - Table name to find in AST. + * @param {Object} ast - AST of SELECT. + * @param {String} astBlock - AST property to search. + * @returns {String} - Alias name or "" if not found. + */ + static locateAstTableAlias(tableName, ast, astBlock) { + if (typeof ast[astBlock] === 'undefined') + return ""; + + let block = [ast[astBlock]]; + if (TableAlias.isIterable(ast[astBlock])) { + block = ast[astBlock]; + } + + for (const astItem of block) { + if (typeof astItem.table === 'string' && tableName === astItem.table.toUpperCase() && astItem.as !== "") { + return astItem.as; + } + } + + return ""; + } + + /** + * Check if input is iterable. + * @param {any} input - Check this object to see if it can be iterated. + * @returns {Boolean} - true - can be iterated. false - cannot be iterated. + */ + static isIterable(input) { + if (input === null || input === undefined) { + return false } - return aliasNameFound; + return typeof input[Symbol.iterator] === 'function' } /** @@ -513,7 +800,7 @@ class Sql { while (extractedAlias === "" && i < astRecursiveTableBlocks.length) { if (typeof ast[astRecursiveTableBlocks[i]] !== 'undefined') { for (const unionAst of ast[astRecursiveTableBlocks[i]]) { - extractedAlias = Sql.getTableAlias(tableName, unionAst); + extractedAlias = TableAlias.getTableAlias(tableName, unionAst); if (extractedAlias !== "") break; @@ -535,11 +822,11 @@ class Sql { static getTableAliasWhereIn(tableAlias, tableName, ast) { let extractedAlias = tableAlias; if (tableAlias === "" && typeof ast.WHERE !== 'undefined' && ast.WHERE.operator === "IN") { - extractedAlias = Sql.getTableAlias(tableName, ast.WHERE.right); + extractedAlias = TableAlias.getTableAlias(tableName, ast.WHERE.right); } if (extractedAlias === "" && ast.operator === "IN") { - extractedAlias = Sql.getTableAlias(tableName, ast.right); + extractedAlias = TableAlias.getTableAlias(tableName, ast.right); } return extractedAlias; @@ -557,62 +844,27 @@ class Sql { if (tableAlias === "" && typeof ast.WHERE !== 'undefined' && typeof ast.WHERE.terms !== 'undefined') { for (const term of ast.WHERE.terms) { if (extractedTableAlias === "") - extractedTableAlias = Sql.getTableAlias(tableName, term); + extractedTableAlias = TableAlias.getTableAlias(tableName, term); } } return extractedTableAlias; } +} - /** - * Create table definition array from select string. - * @param {String} statement - full sql select statement. - * @returns {String[][]} - table definition array. - */ - static getReferencedTableNames(statement) { - const ast = SqlParse.sql2ast(statement); - return this.getReferencedTableNamesFromAst(ast); - } - - /** - * Create table definition array from select AST. - * @param {Object} ast - AST for SELECT. - * @returns {any[]} - table definition array. - * * [0] - table name. - * * [1] - sheet tab name - * * [2] - cache seconds - * * [3] - output column title flag - */ - static getReferencedTableNamesFromAst(ast) { - const DEFAULT_CACHE_SECONDS = 60; - const DEFAULT_COLUMNS_OUTPUT = true; - const tableSet = new Map(); - - Sql.extractAstTables(ast, tableSet); - - const tableList = []; - // @ts-ignore - for (const key of tableSet.keys()) { - const tableDef = [key, key, DEFAULT_CACHE_SECONDS, DEFAULT_COLUMNS_OUTPUT]; - - tableList.push(tableDef); - } - - return tableList; - } - +class TableExtract { /** * Search for all referenced tables in SELECT. * @param {Object} ast - AST for SELECT. * @param {Map} tableSet - Function updates this map of table names and alias name. */ static extractAstTables(ast, tableSet) { - Sql.getTableNamesFrom(ast, tableSet); - Sql.getTableNamesJoin(ast, tableSet); - Sql.getTableNamesUnion(ast, tableSet); - Sql.getTableNamesWhereIn(ast, tableSet); - Sql.getTableNamesWhereTerms(ast, tableSet); - Sql.getTableNamesCorrelatedSelect(ast, tableSet); + TableExtract.getTableNamesFrom(ast, tableSet); + TableExtract.getTableNamesJoin(ast, tableSet); + TableExtract.getTableNamesUnion(ast, tableSet); + TableExtract.getTableNamesWhereIn(ast, tableSet); + TableExtract.getTableNamesWhereTerms(ast, tableSet); + TableExtract.getTableNamesCorrelatedSelect(ast, tableSet); } /** @@ -627,7 +879,7 @@ class Sql { tableSet.set(fromAst.table.toUpperCase(), typeof fromAst.as === 'undefined' ? '' : fromAst.as.toUpperCase()); } else { - Sql.extractAstTables(fromAst.FROM, tableSet); + TableExtract.extractAstTables(fromAst.FROM, tableSet); } fromAst = fromAst.FROM; } @@ -647,24 +899,11 @@ class Sql { tableSet.set(astItem.table.toUpperCase(), typeof astItem.as === 'undefined' ? '' : astItem.as.toUpperCase()); } else { - Sql.extractAstTables(astItem.table, tableSet); + TableExtract.extractAstTables(astItem.table, tableSet); } } } - /** - * Check if input is iterable. - * @param {any} input - Check this object to see if it can be iterated. - * @returns {Boolean} - true - can be iterated. false - cannot be iterated. - */ - static isIterable(input) { - if (input === null || input === undefined) { - return false - } - - return typeof input[Symbol.iterator] === 'function' - } - /** * Searches for table names within SELECT (union, intersect, except) statements. * @param {Object} ast - AST for SELECT @@ -728,7 +967,7 @@ class Sql { } if (typeof ast.terms !== 'undefined') { for (const term of ast.terms) { - Sql.getTableNamesWhereCondition(term, tableSet); + TableExtract.getTableNamesWhereCondition(term, tableSet); } } } @@ -747,230 +986,60 @@ class Sql { } } } +} +class SqlSets { /** - * Search a property of AST for table alias name. - * @param {String} tableName - Table name to find in AST. - * @param {Object} ast - AST of SELECT. - * @param {String} astBlock - AST property to search. - * @returns {String} - Alias name or "" if not found. - */ - static locateAstTableAlias(tableName, ast, astBlock) { - if (typeof ast[astBlock] === 'undefined') - return ""; - - let block = [ast[astBlock]]; - if (this.isIterable(ast[astBlock])) { - block = ast[astBlock]; - } - - for (const astItem of block) { - if (typeof astItem.table === 'string' && tableName === astItem.table.toUpperCase() && astItem.as !== "") { - return astItem.as; - } - } - - return ""; - } - - /** - * Load SELECT data and return in double array. - * @param {Object} selectAst - Abstract Syntax Tree of SELECT - * @returns {any[][]} - double array useable by Google Sheet in custom function return value. - * * First row of data will be column name if column title output was requested. - * * First Array Index - ROW - * * Second Array Index - COLUMN - */ - select(selectAst) { - let recordIDs = []; - let viewTableData = []; - let ast = selectAst; - - if (typeof ast.FROM === 'undefined') - throw new Error("Missing keyword FROM"); - - // Manipulate AST to add GROUP BY if DISTINCT keyword. - ast = Sql.distinctField(ast); - - // Manipulate AST add pivot fields. - ast = this.pivotField(ast); - - const view = new SelectTables(ast, this.tables, this.bindData); - - // JOIN tables to create a derived table. - view.join(ast); // skipcq: JS-D008 - - view.updateSelectedFields(ast); - - // Get the record ID's of all records matching WHERE condition. - recordIDs = view.whereCondition(ast); - - // Get selected data records. - viewTableData = view.getViewData(recordIDs); - - // Compress the data. - viewTableData = view.groupBy(ast, viewTableData); - - // Sort our selected data. - view.orderBy(ast, viewTableData); - - // Remove fields referenced but not included in SELECT field list. - view.removeTempColumns(viewTableData); - - // Limit rows returned. - viewTableData = SelectTables.limit(ast, viewTableData); - - // Apply SET rules for various union types. - viewTableData = this.unionSets(ast, viewTableData); - - // Add column titles - if (this.columnTitle) { - viewTableData.unshift(view.getColumnTitles(this.columnTableNameReplacement)); - } - - // If no data and no titles, create empty double array so sheets function does not have an error. - if (viewTableData.length === 0) { - viewTableData.push([""]); - } - - if (viewTableData.length === 1 && viewTableData[0].length === 0) { - viewTableData[0] = [""]; - } - - return viewTableData; - } - - /** - * If 'GROUP BY' is not set and 'DISTINCT' column is specified, update AST to add 'GROUP BY'. - * @param {Object} ast - Abstract Syntax Tree for SELECT. - * @returns {Object} - Updated AST to include GROUP BY when DISTINCT field used. - */ - static distinctField(ast) { - const astFields = ast.SELECT; - - if (astFields.length === 0) - return ast; - - const firstField = astFields[0].name.toUpperCase(); - if (firstField.startsWith("DISTINCT")) { - astFields[0].name = firstField.replace("DISTINCT", "").trim(); - - if (typeof ast['GROUP BY'] === 'undefined') { - const groupBy = []; - - for (const astItem of astFields) { - groupBy.push({ name: astItem.name, as: '' }); - } - - ast["GROUP BY"] = groupBy; - } - } - - return ast; - } - - /** - * Add new column to AST for every AGGREGATE function and unique pivot column data. - * @param {Object} ast - AST which is checked to see if a PIVOT is used. - * @returns {Object} - Updated AST containing SELECT FIELDS for the pivot data OR original AST if no pivot. - */ - pivotField(ast) { - // If we are doing a PIVOT, it then requires a GROUP BY. - if (typeof ast.PIVOT !== 'undefined') { - if (typeof ast['GROUP BY'] === 'undefined') - throw new Error("PIVOT requires GROUP BY"); - } - else { - return ast; - } - - // These are all of the unique PIVOT field data points. - const pivotFieldData = this.getUniquePivotData(ast); - - ast.SELECT = Sql.addCalculatedPivotFieldsToAst(ast, pivotFieldData); - - return ast; - } - - /** - * Find distinct pivot column data. - * @param {Object} ast - Abstract Syntax Tree containing the PIVOT option. - * @returns {any[][]} - All unique data points found in the PIVOT field for the given SELECT. + * + * @param {Object} ast + * @param {BindData} bindData + * @param {Map} tableMap */ - getUniquePivotData(ast) { - const pivotAST = {}; - - pivotAST.SELECT = ast.PIVOT; - pivotAST.SELECT[0].name = `DISTINCT ${pivotAST.SELECT[0].name}`; - pivotAST.FROM = ast.FROM; - pivotAST.WHERE = ast.WHERE; - - const pivotSql = new Sql() - .enableColumnTitle(false) - .setBindValues(this.bindData) - .copyTableData(this.getTables()); - - // These are all of the unique PIVOT field data points. - const tableData = pivotSql.execute(pivotAST); - - return tableData; + constructor(ast, bindData, tableMap) { + this.ast = ast; + this.bindData = bindData; + this.tableMap = tableMap; + this.unionTypes = ['UNION', 'UNION ALL', 'INTERSECT', 'EXCEPT']; } /** - * Add new calculated fields to the existing SELECT fields. A field is add for each combination of - * aggregate function and unqiue pivot data points. The CASE function is used for each new field. - * A test is made if the column data equal the pivot data. If it is, the aggregate function data - * is returned, otherwise null. The GROUP BY is later applied and the appropiate pivot data will - * be calculated. - * @param {Object} ast - AST to be updated. - * @param {any[][]} pivotFieldData - Table data with unique pivot field data points. - * @returns {Object} - Abstract Sytax Tree with new SELECT fields with a CASE for each pivot data and aggregate function. + * + * @param {Object} ast + * @returns {Boolean} */ - static addCalculatedPivotFieldsToAst(ast, pivotFieldData) { - const newPivotAstFields = []; - - for (const selectField of ast.SELECT) { - // If this is an aggregrate function, we will add one for every pivotFieldData item - const functionNameRegex = /^\w+\s*(?=\()/; - const matches = selectField.name.match(functionNameRegex) - if (matches !== null && matches.length > 0) { - const args = SelectTables.parseForFunctions(selectField.name, matches[0].trim()); - - for (const fld of pivotFieldData) { - const caseTxt = `${matches[0]}(CASE WHEN ${ast.PIVOT[0].name} = '${fld}' THEN ${args[1]} ELSE 'null' END)`; - const asField = `${fld[0]} ${typeof selectField.as !== 'undefined' && selectField.as !== "" ? selectField.as : selectField.name}`; - newPivotAstFields.push({ name: caseTxt, as: asField }); - } - } - else { - newPivotAstFields.push(selectField); + isSqlSet(ast) { + for (const type of this.unionTypes) { + if (typeof this.ast[type] !== 'undefined') { + return true; } } - return newPivotAstFields; + return false; } /** * If any SET commands are found (like UNION, INTERSECT,...) the additional SELECT is done. The new * data applies the SET rule against the income viewTableData, and the result data set is returned. - * @param {Object} ast - SELECT AST. * @param {any[][]} viewTableData - SELECTED data before UNION. * @returns {any[][]} - New data with set rules applied. */ - unionSets(ast, viewTableData) { - const unionTypes = ['UNION', 'UNION ALL', 'INTERSECT', 'EXCEPT']; + unionSets(viewTableData) { + if (!this.isSqlSet(this.ast)) { + return viewTableData; + } + let unionTableData = viewTableData; - for (const type of unionTypes) { - if (typeof ast[type] === 'undefined') { + for (const type of this.unionTypes) { + if (typeof this.ast[type] === 'undefined') { continue; } const unionSQL = new Sql() .setBindValues(this.bindData) - .copyTableData(this.getTables()); + .copyTableData(this.tableMap); - for (const union of ast[type]) { + for (const union of this.ast[type]) { const unionData = unionSQL.execute(union); if (unionTableData.length > 0 && unionData.length > 0 && unionTableData[0].length !== unionData[0].length) throw new Error(`Invalid ${type}. Selected field counts do not match.`); @@ -978,7 +1047,7 @@ class Sql { switch (type) { case "UNION": unionTableData = unionTableData.concat(unionData); - unionTableData = Sql.removeDuplicateRows(unionTableData); + unionTableData = SqlSets.removeDuplicateRows(unionTableData); break; case "UNION ALL": @@ -988,12 +1057,12 @@ class Sql { case "INTERSECT": // Must exist in BOTH tables. - unionTableData = Sql.intersectRows(unionTableData, unionData); + unionTableData = SqlSets.intersectRows(unionTableData, unionData); break; case "EXCEPT": // Remove from first table all rows that match in second table. - unionTableData = Sql.exceptRows(unionTableData, unionData); + unionTableData = SqlSets.exceptRows(unionTableData, unionData); break; default: @@ -2391,11 +2460,11 @@ class SelectTables { */ static isCorrelatedSubQuery(ast) { const tableSet = new Map(); - Sql.extractAstTables(ast, tableSet); + TableExtract.extractAstTables(ast, tableSet); const tableSetCorrelated = new Map(); if (typeof ast.WHERE !== 'undefined') { - Sql.getTableNamesWhereCondition(ast.WHERE, tableSetCorrelated); + TableExtract.getTableNamesWhereCondition(ast.WHERE, tableSetCorrelated); } // @ts-ignore diff --git a/package-lock.json b/package-lock.json index 0b37709..db74803 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@demmings/gssql", - "version": "1.3.25", + "version": "1.3.26", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@demmings/gssql", - "version": "1.3.25", + "version": "1.3.26", "license": "ISC", "dependencies": { "gas-local": "^1.3.1" diff --git a/package.json b/package.json index c6ef589..e5eabc6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@demmings/gssql", - "version": "1.3.25", + "version": "1.3.26", "description": "Google Sheets QUERY function replacement using real SQL select syntax.", "main": "testGsSql.js", "files": ["./src", "src", "img", "dist"], diff --git a/src/Sql.js b/src/Sql.js index 0b0b224..ec8d890 100644 --- a/src/Sql.js +++ b/src/Sql.js @@ -1,7 +1,7 @@ // @author Chris Demmings - https://demmings.github.io/ /* *** DEBUG START *** // Remove comments for testing in NODE -export { Sql, gsSQL, GasSql, BindData }; +export { Sql, gsSQL, GasSql, BindData, TableExtract }; import { Table } from './Table.js'; import { TableData } from './TableData.js'; import { SqlParse } from './SimpleParser.js'; @@ -367,39 +367,76 @@ class Sql { // updated to use the new table. this.selectJoinSubQuery(); - Sql.setTableAlias(this.tables, this.ast); + TableAlias.setTableAlias(this.tables, this.ast); Sql.loadSchema(this.tables); - if (typeof this.ast.SELECT === 'undefined') { - throw new Error("Only SELECT statements are supported."); - } - return this.select(this.ast); } /** - * Updates 'tables' with table column information. - * @param {Map} tables + * Modifies AST when FROM is a sub-query rather than a table name. */ - static loadSchema(tables) { - // @ts-ignore - for (const table of tables.keys()) { - const tableInfo = tables.get(table.toUpperCase()); - tableInfo.loadSchema(); + selectFromSubQuery() { + if (typeof this.ast.FROM !== 'undefined' && typeof this.ast.FROM.SELECT !== 'undefined') { + const data = new Sql() + .setTables(this.tables) + .enableColumnTitle(true) + .replaceColumnTableNameWith(this.ast.FROM.table) + .execute(this.ast.FROM); + + if (typeof this.ast.FROM.table !== 'undefined') { + this.addTableData(this.ast.FROM.table, data); + } + + if (this.ast.FROM.table === '') { + throw new Error("Every derived table must have its own alias"); + } + + this.ast.FROM.as = ''; } } + /** - * Updates 'tables' with associated table ALIAS name found in ast. + * Checks if the JOINed table is a sub-query. + * The sub-query is evaluated and assigned the alias name. + * The AST is adjusted to use the new JOIN TABLE. + * @returns {void} + */ + selectJoinSubQuery() { + if (typeof this.ast.JOIN === 'undefined') + return; + + for (const joinAst of this.ast.JOIN) { + if (typeof joinAst.table !== 'string') { + const data = new Sql() + .setTables(this.tables) + .enableColumnTitle(true) + .replaceColumnTableNameWith(joinAst.as) + .execute(joinAst.table); + + if (typeof joinAst.as !== 'undefined') { + this.addTableData(joinAst.as, data); + } + + if (joinAst.as === '') { + throw new Error("Every derived table must have its own alias"); + } + joinAst.table = joinAst.as; + joinAst.as = ''; + } + } + } + + /** + * Updates 'tables' with table column information. * @param {Map} tables - * @param {Object} ast */ - static setTableAlias(tables, ast) { + static loadSchema(tables) { // @ts-ignore for (const table of tables.keys()) { - const tableAlias = Sql.getTableAlias(table, ast); const tableInfo = tables.get(table.toUpperCase()); - tableInfo.setTableAlias(tableAlias); + tableInfo.loadSchema(); } } @@ -421,75 +458,286 @@ class Sql { } /** - * Find table alias name (if any) for input actual table name. - * @param {String} tableName - Actual table name. - * @param {Object} ast - Abstract Syntax Tree for SQL. - * @returns {String} - Table alias. Empty string if not found. - */ - static getTableAlias(tableName, ast) { - let tableAlias = ""; - const ucTableName = tableName.toUpperCase(); + * Create table definition array from select string. + * @param {String} statement - full sql select statement. + * @returns {String[][]} - table definition array. + */ + static getReferencedTableNames(statement) { + const ast = SqlParse.sql2ast(statement); + return this.getReferencedTableNamesFromAst(ast); + } - tableAlias = Sql.getTableAliasFromJoin(tableAlias, ucTableName, ast); - tableAlias = Sql.getTableAliasUnion(tableAlias, ucTableName, ast); - tableAlias = Sql.getTableAliasWhereIn(tableAlias, ucTableName, ast); - tableAlias = Sql.getTableAliasWhereTerms(tableAlias, ucTableName, ast); + /** + * Create table definition array from select AST. + * @param {Object} ast - AST for SELECT. + * @returns {any[]} - table definition array. + * * [0] - table name. + * * [1] - sheet tab name + * * [2] - cache seconds + * * [3] - output column title flag + */ + static getReferencedTableNamesFromAst(ast) { + const DEFAULT_CACHE_SECONDS = 60; + const DEFAULT_COLUMNS_OUTPUT = true; + const tableSet = new Map(); - return tableAlias; + TableExtract.extractAstTables(ast, tableSet); + + const tableList = []; + // @ts-ignore + for (const key of tableSet.keys()) { + const tableDef = [key, key, DEFAULT_CACHE_SECONDS, DEFAULT_COLUMNS_OUTPUT]; + + tableList.push(tableDef); + } + + return tableList; } /** - * Modifies AST when FROM is a sub-query rather than a table name. + * Load SELECT data and return in double array. + * @param {Object} selectAst - Abstract Syntax Tree of SELECT + * @returns {any[][]} - double array useable by Google Sheet in custom function return value. + * * First row of data will be column name if column title output was requested. + * * First Array Index - ROW + * * Second Array Index - COLUMN */ - selectFromSubQuery() { - if (typeof this.ast.FROM !== 'undefined' && typeof this.ast.FROM.SELECT !== 'undefined') { - const data = new Sql() - .setTables(this.tables) - .enableColumnTitle(true) - .replaceColumnTableNameWith(this.ast.FROM.table) - .execute(this.ast.FROM); + select(selectAst) { + let ast = selectAst; - if (typeof this.ast.FROM.table !== 'undefined') { - this.addTableData(this.ast.FROM.table, data); - } + Sql.errorCheckSelectAST(ast); - if (this.ast.FROM.table === '') { - throw new Error("Every derived table must have its own alias"); - } + // Manipulate AST to add GROUP BY if DISTINCT keyword. + ast = Sql.distinctField(ast); - this.ast.FROM.as = ''; + // Manipulate AST add pivot fields. + ast = this.pivotField(ast); + + const view = new SelectTables(ast, this.tables, this.bindData); + + // JOIN tables to create a derived table. + view.join(ast); // skipcq: JS-D008 + + view.updateSelectedFields(ast); + + // Get the record ID's of all records matching WHERE condition. + const recordIDs = view.whereCondition(ast); + + // Get selected data records. + let viewTableData = view.getViewData(recordIDs); + + // Compress the data. + viewTableData = view.groupBy(ast, viewTableData); + + // Sort our selected data. + view.orderBy(ast, viewTableData); + + // Remove fields referenced but not included in SELECT field list. + view.removeTempColumns(viewTableData); + + // Limit rows returned. + viewTableData = SelectTables.limit(ast, viewTableData); + + // Apply SET rules for various union types. + const sqlSet = new SqlSets(ast, this.bindData, this.getTables()); + viewTableData = sqlSet.unionSets(viewTableData); + + // Add column titles + viewTableData = this.addColumnTitles(viewTableData, view); + + // Deal with empty dataset. + viewTableData = this.cleanUp(viewTableData); + + return viewTableData; + } + + /** + * Basic sanity check of AST for a SELECT statement. + * @param {object} ast + */ + static errorCheckSelectAST(ast) { + if (typeof ast.SELECT === 'undefined') { + throw new Error("Only SELECT statements are supported."); + } + + if (typeof ast.FROM === 'undefined') { + throw new Error("Missing keyword FROM"); } } /** - * Checks if the JOINed table is a sub-query. - * The sub-query is evaluated and assigned the alias name. - * The AST is adjusted to use the new JOIN TABLE. - * @returns {void} + * If 'GROUP BY' is not set and 'DISTINCT' column is specified, update AST to add 'GROUP BY'. + * @param {Object} ast - Abstract Syntax Tree for SELECT. + * @returns {Object} - Updated AST to include GROUP BY when DISTINCT field used. */ - selectJoinSubQuery() { - if (typeof this.ast.JOIN === 'undefined') - return; + static distinctField(ast) { + const astFields = ast.SELECT; - for (const joinAst of this.ast.JOIN) { - if (typeof joinAst.table !== 'string') { - const data = new Sql() - .setTables(this.tables) - .enableColumnTitle(true) - .replaceColumnTableNameWith(joinAst.as) - .execute(joinAst.table); + if (astFields.length === 0) + return ast; - if (typeof joinAst.as !== 'undefined') { - this.addTableData(joinAst.as, data); + const firstField = astFields[0].name.toUpperCase(); + if (firstField.startsWith("DISTINCT")) { + astFields[0].name = firstField.replace("DISTINCT", "").trim(); + + if (typeof ast['GROUP BY'] === 'undefined') { + const groupBy = []; + + for (const astItem of astFields) { + groupBy.push({ name: astItem.name, as: '' }); } - if (joinAst.as === '') { - throw new Error("Every derived table must have its own alias"); + ast["GROUP BY"] = groupBy; + } + } + + return ast; + } + + /** + * Add new column to AST for every AGGREGATE function and unique pivot column data. + * @param {Object} ast - AST which is checked to see if a PIVOT is used. + * @returns {Object} - Updated AST containing SELECT FIELDS for the pivot data OR original AST if no pivot. + */ + pivotField(ast) { + // If we are doing a PIVOT, it then requires a GROUP BY. + if (typeof ast.PIVOT !== 'undefined') { + if (typeof ast['GROUP BY'] === 'undefined') + throw new Error("PIVOT requires GROUP BY"); + } + else { + return ast; + } + + // These are all of the unique PIVOT field data points. + const pivotFieldData = this.getUniquePivotData(ast); + + ast.SELECT = Sql.addCalculatedPivotFieldsToAst(ast, pivotFieldData); + + return ast; + } + + /** + * Find distinct pivot column data. + * @param {Object} ast - Abstract Syntax Tree containing the PIVOT option. + * @returns {any[][]} - All unique data points found in the PIVOT field for the given SELECT. + */ + getUniquePivotData(ast) { + const pivotAST = {}; + + pivotAST.SELECT = ast.PIVOT; + pivotAST.SELECT[0].name = `DISTINCT ${pivotAST.SELECT[0].name}`; + pivotAST.FROM = ast.FROM; + pivotAST.WHERE = ast.WHERE; + + const pivotSql = new Sql() + .enableColumnTitle(false) + .setBindValues(this.bindData) + .copyTableData(this.getTables()); + + // These are all of the unique PIVOT field data points. + const tableData = pivotSql.execute(pivotAST); + + return tableData; + } + + /** + * Add new calculated fields to the existing SELECT fields. A field is add for each combination of + * aggregate function and unqiue pivot data points. The CASE function is used for each new field. + * A test is made if the column data equal the pivot data. If it is, the aggregate function data + * is returned, otherwise null. The GROUP BY is later applied and the appropiate pivot data will + * be calculated. + * @param {Object} ast - AST to be updated. + * @param {any[][]} pivotFieldData - Table data with unique pivot field data points. + * @returns {Object} - Abstract Sytax Tree with new SELECT fields with a CASE for each pivot data and aggregate function. + */ + static addCalculatedPivotFieldsToAst(ast, pivotFieldData) { + const newPivotAstFields = []; + + for (const selectField of ast.SELECT) { + // If this is an aggregrate function, we will add one for every pivotFieldData item + const functionNameRegex = /^\w+\s*(?=\()/; + const matches = selectField.name.match(functionNameRegex) + if (matches !== null && matches.length > 0) { + const args = SelectTables.parseForFunctions(selectField.name, matches[0].trim()); + + for (const fld of pivotFieldData) { + const caseTxt = `${matches[0]}(CASE WHEN ${ast.PIVOT[0].name} = '${fld}' THEN ${args[1]} ELSE 'null' END)`; + const asField = `${fld[0]} ${typeof selectField.as !== 'undefined' && selectField.as !== "" ? selectField.as : selectField.name}`; + newPivotAstFields.push({ name: caseTxt, as: asField }); } - joinAst.table = joinAst.as; - joinAst.as = ''; } + else { + newPivotAstFields.push(selectField); + } + } + + return newPivotAstFields; + } + + /** + * Add column titles to data if needed. + * @param {any[][]} viewTableData + * @param {SelectTables} view + * @returns {any[][]} + */ + addColumnTitles(viewTableData, view) { + if (this.columnTitle) { + viewTableData.unshift(view.getColumnTitles(this.columnTableNameReplacement)); } + + return viewTableData; + } + + /** + * If no data and no titles, create empty double array so sheets function does not have an error. + * @param {any[][]} viewTableData + * @returns {any[][]} + */ + cleanUp(viewTableData) { + if (viewTableData.length === 0) { + viewTableData.push([""]); + } + + if (viewTableData.length === 1 && viewTableData[0].length === 0) { + viewTableData[0] = [""]; + } + + return viewTableData; + } +} + +class TableAlias { + /** + * Updates 'tables' with associated table ALIAS name found in ast. + * @param {Map} tables + * @param {Object} ast + */ + static setTableAlias(tables, ast) { + // @ts-ignore + for (const table of tables.keys()) { + const tableAlias = TableAlias.getTableAlias(table, ast); + const tableInfo = tables.get(table.toUpperCase()); + tableInfo.setTableAlias(tableAlias); + } + } + + /** + * Find table alias name (if any) for input actual table name. + * @param {String} tableName - Actual table name. + * @param {Object} ast - Abstract Syntax Tree for SQL. + * @returns {String} - Table alias. Empty string if not found. + */ + static getTableAlias(tableName, ast) { + let tableAlias = ""; + const ucTableName = tableName.toUpperCase(); + + tableAlias = TableAlias.getTableAliasFromJoin(tableAlias, ucTableName, ast); + tableAlias = TableAlias.getTableAliasUnion(tableAlias, ucTableName, ast); + tableAlias = TableAlias.getTableAliasWhereIn(tableAlias, ucTableName, ast); + tableAlias = TableAlias.getTableAliasWhereTerms(tableAlias, ucTableName, ast); + + return tableAlias; } /** @@ -503,13 +751,52 @@ class Sql { const astTableBlocks = ['FROM', 'JOIN']; let aliasNameFound = tableAlias; - let i = 0; - while (aliasNameFound === "" && i < astTableBlocks.length) { - aliasNameFound = Sql.locateAstTableAlias(tableName, ast, astTableBlocks[i]); - i++; + let i = 0; + while (aliasNameFound === "" && i < astTableBlocks.length) { + aliasNameFound = TableAlias.locateAstTableAlias(tableName, ast, astTableBlocks[i]); + i++; + } + + return aliasNameFound; + } + + + /** + * Search a property of AST for table alias name. + * @param {String} tableName - Table name to find in AST. + * @param {Object} ast - AST of SELECT. + * @param {String} astBlock - AST property to search. + * @returns {String} - Alias name or "" if not found. + */ + static locateAstTableAlias(tableName, ast, astBlock) { + if (typeof ast[astBlock] === 'undefined') + return ""; + + let block = [ast[astBlock]]; + if (TableAlias.isIterable(ast[astBlock])) { + block = ast[astBlock]; + } + + for (const astItem of block) { + if (typeof astItem.table === 'string' && tableName === astItem.table.toUpperCase() && astItem.as !== "") { + return astItem.as; + } + } + + return ""; + } + + /** + * Check if input is iterable. + * @param {any} input - Check this object to see if it can be iterated. + * @returns {Boolean} - true - can be iterated. false - cannot be iterated. + */ + static isIterable(input) { + if (input === null || input === undefined) { + return false } - return aliasNameFound; + return typeof input[Symbol.iterator] === 'function' } /** @@ -527,7 +814,7 @@ class Sql { while (extractedAlias === "" && i < astRecursiveTableBlocks.length) { if (typeof ast[astRecursiveTableBlocks[i]] !== 'undefined') { for (const unionAst of ast[astRecursiveTableBlocks[i]]) { - extractedAlias = Sql.getTableAlias(tableName, unionAst); + extractedAlias = TableAlias.getTableAlias(tableName, unionAst); if (extractedAlias !== "") break; @@ -549,11 +836,11 @@ class Sql { static getTableAliasWhereIn(tableAlias, tableName, ast) { let extractedAlias = tableAlias; if (tableAlias === "" && typeof ast.WHERE !== 'undefined' && ast.WHERE.operator === "IN") { - extractedAlias = Sql.getTableAlias(tableName, ast.WHERE.right); + extractedAlias = TableAlias.getTableAlias(tableName, ast.WHERE.right); } if (extractedAlias === "" && ast.operator === "IN") { - extractedAlias = Sql.getTableAlias(tableName, ast.right); + extractedAlias = TableAlias.getTableAlias(tableName, ast.right); } return extractedAlias; @@ -571,62 +858,27 @@ class Sql { if (tableAlias === "" && typeof ast.WHERE !== 'undefined' && typeof ast.WHERE.terms !== 'undefined') { for (const term of ast.WHERE.terms) { if (extractedTableAlias === "") - extractedTableAlias = Sql.getTableAlias(tableName, term); + extractedTableAlias = TableAlias.getTableAlias(tableName, term); } } return extractedTableAlias; } +} - /** - * Create table definition array from select string. - * @param {String} statement - full sql select statement. - * @returns {String[][]} - table definition array. - */ - static getReferencedTableNames(statement) { - const ast = SqlParse.sql2ast(statement); - return this.getReferencedTableNamesFromAst(ast); - } - - /** - * Create table definition array from select AST. - * @param {Object} ast - AST for SELECT. - * @returns {any[]} - table definition array. - * * [0] - table name. - * * [1] - sheet tab name - * * [2] - cache seconds - * * [3] - output column title flag - */ - static getReferencedTableNamesFromAst(ast) { - const DEFAULT_CACHE_SECONDS = 60; - const DEFAULT_COLUMNS_OUTPUT = true; - const tableSet = new Map(); - - Sql.extractAstTables(ast, tableSet); - - const tableList = []; - // @ts-ignore - for (const key of tableSet.keys()) { - const tableDef = [key, key, DEFAULT_CACHE_SECONDS, DEFAULT_COLUMNS_OUTPUT]; - - tableList.push(tableDef); - } - - return tableList; - } - +class TableExtract { /** * Search for all referenced tables in SELECT. * @param {Object} ast - AST for SELECT. * @param {Map} tableSet - Function updates this map of table names and alias name. */ static extractAstTables(ast, tableSet) { - Sql.getTableNamesFrom(ast, tableSet); - Sql.getTableNamesJoin(ast, tableSet); - Sql.getTableNamesUnion(ast, tableSet); - Sql.getTableNamesWhereIn(ast, tableSet); - Sql.getTableNamesWhereTerms(ast, tableSet); - Sql.getTableNamesCorrelatedSelect(ast, tableSet); + TableExtract.getTableNamesFrom(ast, tableSet); + TableExtract.getTableNamesJoin(ast, tableSet); + TableExtract.getTableNamesUnion(ast, tableSet); + TableExtract.getTableNamesWhereIn(ast, tableSet); + TableExtract.getTableNamesWhereTerms(ast, tableSet); + TableExtract.getTableNamesCorrelatedSelect(ast, tableSet); } /** @@ -641,7 +893,7 @@ class Sql { tableSet.set(fromAst.table.toUpperCase(), typeof fromAst.as === 'undefined' ? '' : fromAst.as.toUpperCase()); } else { - Sql.extractAstTables(fromAst.FROM, tableSet); + TableExtract.extractAstTables(fromAst.FROM, tableSet); } fromAst = fromAst.FROM; } @@ -661,24 +913,11 @@ class Sql { tableSet.set(astItem.table.toUpperCase(), typeof astItem.as === 'undefined' ? '' : astItem.as.toUpperCase()); } else { - Sql.extractAstTables(astItem.table, tableSet); + TableExtract.extractAstTables(astItem.table, tableSet); } } } - /** - * Check if input is iterable. - * @param {any} input - Check this object to see if it can be iterated. - * @returns {Boolean} - true - can be iterated. false - cannot be iterated. - */ - static isIterable(input) { - if (input === null || input === undefined) { - return false - } - - return typeof input[Symbol.iterator] === 'function' - } - /** * Searches for table names within SELECT (union, intersect, except) statements. * @param {Object} ast - AST for SELECT @@ -742,7 +981,7 @@ class Sql { } if (typeof ast.terms !== 'undefined') { for (const term of ast.terms) { - Sql.getTableNamesWhereCondition(term, tableSet); + TableExtract.getTableNamesWhereCondition(term, tableSet); } } } @@ -761,230 +1000,60 @@ class Sql { } } } +} +class SqlSets { /** - * Search a property of AST for table alias name. - * @param {String} tableName - Table name to find in AST. - * @param {Object} ast - AST of SELECT. - * @param {String} astBlock - AST property to search. - * @returns {String} - Alias name or "" if not found. - */ - static locateAstTableAlias(tableName, ast, astBlock) { - if (typeof ast[astBlock] === 'undefined') - return ""; - - let block = [ast[astBlock]]; - if (this.isIterable(ast[astBlock])) { - block = ast[astBlock]; - } - - for (const astItem of block) { - if (typeof astItem.table === 'string' && tableName === astItem.table.toUpperCase() && astItem.as !== "") { - return astItem.as; - } - } - - return ""; - } - - /** - * Load SELECT data and return in double array. - * @param {Object} selectAst - Abstract Syntax Tree of SELECT - * @returns {any[][]} - double array useable by Google Sheet in custom function return value. - * * First row of data will be column name if column title output was requested. - * * First Array Index - ROW - * * Second Array Index - COLUMN - */ - select(selectAst) { - let recordIDs = []; - let viewTableData = []; - let ast = selectAst; - - if (typeof ast.FROM === 'undefined') - throw new Error("Missing keyword FROM"); - - // Manipulate AST to add GROUP BY if DISTINCT keyword. - ast = Sql.distinctField(ast); - - // Manipulate AST add pivot fields. - ast = this.pivotField(ast); - - const view = new SelectTables(ast, this.tables, this.bindData); - - // JOIN tables to create a derived table. - view.join(ast); // skipcq: JS-D008 - - view.updateSelectedFields(ast); - - // Get the record ID's of all records matching WHERE condition. - recordIDs = view.whereCondition(ast); - - // Get selected data records. - viewTableData = view.getViewData(recordIDs); - - // Compress the data. - viewTableData = view.groupBy(ast, viewTableData); - - // Sort our selected data. - view.orderBy(ast, viewTableData); - - // Remove fields referenced but not included in SELECT field list. - view.removeTempColumns(viewTableData); - - // Limit rows returned. - viewTableData = SelectTables.limit(ast, viewTableData); - - // Apply SET rules for various union types. - viewTableData = this.unionSets(ast, viewTableData); - - // Add column titles - if (this.columnTitle) { - viewTableData.unshift(view.getColumnTitles(this.columnTableNameReplacement)); - } - - // If no data and no titles, create empty double array so sheets function does not have an error. - if (viewTableData.length === 0) { - viewTableData.push([""]); - } - - if (viewTableData.length === 1 && viewTableData[0].length === 0) { - viewTableData[0] = [""]; - } - - return viewTableData; - } - - /** - * If 'GROUP BY' is not set and 'DISTINCT' column is specified, update AST to add 'GROUP BY'. - * @param {Object} ast - Abstract Syntax Tree for SELECT. - * @returns {Object} - Updated AST to include GROUP BY when DISTINCT field used. - */ - static distinctField(ast) { - const astFields = ast.SELECT; - - if (astFields.length === 0) - return ast; - - const firstField = astFields[0].name.toUpperCase(); - if (firstField.startsWith("DISTINCT")) { - astFields[0].name = firstField.replace("DISTINCT", "").trim(); - - if (typeof ast['GROUP BY'] === 'undefined') { - const groupBy = []; - - for (const astItem of astFields) { - groupBy.push({ name: astItem.name, as: '' }); - } - - ast["GROUP BY"] = groupBy; - } - } - - return ast; - } - - /** - * Add new column to AST for every AGGREGATE function and unique pivot column data. - * @param {Object} ast - AST which is checked to see if a PIVOT is used. - * @returns {Object} - Updated AST containing SELECT FIELDS for the pivot data OR original AST if no pivot. - */ - pivotField(ast) { - // If we are doing a PIVOT, it then requires a GROUP BY. - if (typeof ast.PIVOT !== 'undefined') { - if (typeof ast['GROUP BY'] === 'undefined') - throw new Error("PIVOT requires GROUP BY"); - } - else { - return ast; - } - - // These are all of the unique PIVOT field data points. - const pivotFieldData = this.getUniquePivotData(ast); - - ast.SELECT = Sql.addCalculatedPivotFieldsToAst(ast, pivotFieldData); - - return ast; - } - - /** - * Find distinct pivot column data. - * @param {Object} ast - Abstract Syntax Tree containing the PIVOT option. - * @returns {any[][]} - All unique data points found in the PIVOT field for the given SELECT. + * + * @param {Object} ast + * @param {BindData} bindData + * @param {Map} tableMap */ - getUniquePivotData(ast) { - const pivotAST = {}; - - pivotAST.SELECT = ast.PIVOT; - pivotAST.SELECT[0].name = `DISTINCT ${pivotAST.SELECT[0].name}`; - pivotAST.FROM = ast.FROM; - pivotAST.WHERE = ast.WHERE; - - const pivotSql = new Sql() - .enableColumnTitle(false) - .setBindValues(this.bindData) - .copyTableData(this.getTables()); - - // These are all of the unique PIVOT field data points. - const tableData = pivotSql.execute(pivotAST); - - return tableData; + constructor(ast, bindData, tableMap) { + this.ast = ast; + this.bindData = bindData; + this.tableMap = tableMap; + this.unionTypes = ['UNION', 'UNION ALL', 'INTERSECT', 'EXCEPT']; } /** - * Add new calculated fields to the existing SELECT fields. A field is add for each combination of - * aggregate function and unqiue pivot data points. The CASE function is used for each new field. - * A test is made if the column data equal the pivot data. If it is, the aggregate function data - * is returned, otherwise null. The GROUP BY is later applied and the appropiate pivot data will - * be calculated. - * @param {Object} ast - AST to be updated. - * @param {any[][]} pivotFieldData - Table data with unique pivot field data points. - * @returns {Object} - Abstract Sytax Tree with new SELECT fields with a CASE for each pivot data and aggregate function. + * + * @param {Object} ast + * @returns {Boolean} */ - static addCalculatedPivotFieldsToAst(ast, pivotFieldData) { - const newPivotAstFields = []; - - for (const selectField of ast.SELECT) { - // If this is an aggregrate function, we will add one for every pivotFieldData item - const functionNameRegex = /^\w+\s*(?=\()/; - const matches = selectField.name.match(functionNameRegex) - if (matches !== null && matches.length > 0) { - const args = SelectTables.parseForFunctions(selectField.name, matches[0].trim()); - - for (const fld of pivotFieldData) { - const caseTxt = `${matches[0]}(CASE WHEN ${ast.PIVOT[0].name} = '${fld}' THEN ${args[1]} ELSE 'null' END)`; - const asField = `${fld[0]} ${typeof selectField.as !== 'undefined' && selectField.as !== "" ? selectField.as : selectField.name}`; - newPivotAstFields.push({ name: caseTxt, as: asField }); - } - } - else { - newPivotAstFields.push(selectField); + isSqlSet(ast) { + for (const type of this.unionTypes) { + if (typeof this.ast[type] !== 'undefined') { + return true; } } - return newPivotAstFields; + return false; } /** * If any SET commands are found (like UNION, INTERSECT,...) the additional SELECT is done. The new * data applies the SET rule against the income viewTableData, and the result data set is returned. - * @param {Object} ast - SELECT AST. * @param {any[][]} viewTableData - SELECTED data before UNION. * @returns {any[][]} - New data with set rules applied. */ - unionSets(ast, viewTableData) { - const unionTypes = ['UNION', 'UNION ALL', 'INTERSECT', 'EXCEPT']; + unionSets(viewTableData) { + if (!this.isSqlSet(this.ast)) { + return viewTableData; + } + let unionTableData = viewTableData; - for (const type of unionTypes) { - if (typeof ast[type] === 'undefined') { + for (const type of this.unionTypes) { + if (typeof this.ast[type] === 'undefined') { continue; } const unionSQL = new Sql() .setBindValues(this.bindData) - .copyTableData(this.getTables()); + .copyTableData(this.tableMap); - for (const union of ast[type]) { + for (const union of this.ast[type]) { const unionData = unionSQL.execute(union); if (unionTableData.length > 0 && unionData.length > 0 && unionTableData[0].length !== unionData[0].length) throw new Error(`Invalid ${type}. Selected field counts do not match.`); @@ -992,7 +1061,7 @@ class Sql { switch (type) { case "UNION": unionTableData = unionTableData.concat(unionData); - unionTableData = Sql.removeDuplicateRows(unionTableData); + unionTableData = SqlSets.removeDuplicateRows(unionTableData); break; case "UNION ALL": @@ -1002,12 +1071,12 @@ class Sql { case "INTERSECT": // Must exist in BOTH tables. - unionTableData = Sql.intersectRows(unionTableData, unionData); + unionTableData = SqlSets.intersectRows(unionTableData, unionData); break; case "EXCEPT": // Remove from first table all rows that match in second table. - unionTableData = Sql.exceptRows(unionTableData, unionData); + unionTableData = SqlSets.exceptRows(unionTableData, unionData); break; default: diff --git a/src/Views.js b/src/Views.js index fa556bc..a860f0a 100644 --- a/src/Views.js +++ b/src/Views.js @@ -3,7 +3,7 @@ export { DERIVEDTABLE, VirtualFields, VirtualField, SelectTables, TableFields, TableField, CalculatedField, SqlServerFunctions, DerivedTable }; import { Table } from './Table.js'; -import { Sql, BindData } from './Sql.js'; +import { Sql, BindData, TableExtract } from './Sql.js'; import { SqlParse } from './SimpleParser.js'; import { JoinTables } from './JoinTables.js'; // *** DEBUG END ***/ @@ -727,11 +727,11 @@ class SelectTables { */ static isCorrelatedSubQuery(ast) { const tableSet = new Map(); - Sql.extractAstTables(ast, tableSet); + TableExtract.extractAstTables(ast, tableSet); const tableSetCorrelated = new Map(); if (typeof ast.WHERE !== 'undefined') { - Sql.getTableNamesWhereCondition(ast.WHERE, tableSetCorrelated); + TableExtract.getTableNamesWhereCondition(ast.WHERE, tableSetCorrelated); } // @ts-ignore