├── .eslintignore ├── .eslintrc.json ├── .flowconfig ├── .gitignore ├── LICENSE ├── README.md ├── azure-pipelines.yml ├── doc ├── building.md ├── screenshots │ ├── Tad-parquet.gif │ ├── tad-metobjects-pivoted.png │ ├── tad-metobjects-unpivoted.png │ ├── tad-movies-pivoted.png │ └── tad-snowflake-preview.png └── site │ ├── CNAME │ ├── css │ ├── bootstrap.min.css │ └── main.css │ ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular.woff │ └── glyphicons-halflings-regular.woff2 │ ├── images │ ├── Tad.png │ ├── Tad@2x.png │ ├── csv-icon.png │ ├── csv-icon@2x.png │ ├── favicon.ico │ ├── favicon2.ico │ ├── ornament.png │ ├── ornament@2x.png │ ├── pivot-icon.png │ ├── pivot-icon@2x.png │ ├── puzzle-icon.png │ ├── puzzle-icon@2x.png │ ├── screenshot.jpg │ └── tad-site-screenshot.png │ ├── index.html │ └── js │ ├── bootstrap.min.js │ └── jquery.min.js ├── lerna.json ├── package-lock.json ├── package.json ├── packages ├── aggtree │ ├── README.md │ ├── jest.config.json │ ├── package.json │ ├── src │ │ ├── PathTree.ts │ │ └── aggtree.ts │ ├── test │ │ ├── __snapshots__ │ │ │ └── aggtree.test.ts.snap │ │ ├── aggtree.test.ts │ │ ├── pathTree.test.ts │ │ └── testUtils.ts │ ├── tsconfig-build.json │ └── tsconfig.json ├── reltab-aws-athena │ ├── jest.config.json │ ├── noodle │ │ ├── ambJoinTest.sql │ │ └── joinTest.sql │ ├── package.json │ ├── src │ │ └── reltab-aws-athena.ts │ ├── test │ │ ├── __snapshots__ │ │ │ └── basic.test.ts.snap │ │ ├── basic.test.ts │ │ └── testUtils.ts │ ├── tsconfig.json │ └── util │ │ └── create_movie_metadata.sql ├── reltab-bigquery │ ├── jest.config.json │ ├── package.json │ ├── src │ │ └── reltab-bigquery.ts │ ├── test.out │ ├── test │ │ ├── __snapshots__ │ │ │ └── basic.test.ts.snap │ │ ├── basic.test.ts │ │ └── testUtils.ts │ └── tsconfig.json ├── reltab-duckdb │ ├── jest.config.json │ ├── package.json │ ├── scripts │ │ └── run-tests-locally.sh │ ├── src │ │ ├── csvimport.ts │ │ ├── reltab-duckdb.ts │ │ └── s3utils.ts │ ├── test │ │ ├── __snapshots__ │ │ │ ├── basic.auto.test.ts.snap │ │ │ ├── histo.auto.test.ts.snap │ │ │ └── ipfs.test.ts.snap │ │ ├── basic.auto.test.ts │ │ ├── histo.auto.test.ts │ │ ├── ipfs.test.ts │ │ ├── support │ │ │ ├── barttest.csv │ │ │ └── sample.csv │ │ └── testUtils.ts │ ├── tsconfig-build.json │ └── tsconfig.json ├── reltab-fs │ ├── jest.config.json │ ├── package.json │ ├── src │ │ └── reltab-fs.ts │ ├── tsconfig-build.json │ └── tsconfig.json ├── reltab-snowflake │ ├── jest.config.json │ ├── package.json │ ├── src │ │ └── reltab-snowflake.ts │ ├── test │ │ ├── __snapshots__ │ │ │ └── basic.test.ts.snap │ │ ├── basic.test.ts │ │ └── testUtils.ts │ └── tsconfig.json ├── reltab-sqlite │ ├── jest.config.json │ ├── package.json │ ├── src │ │ ├── csvimport.ts │ │ └── reltab-sqlite.ts │ ├── test │ │ ├── __snapshots__ │ │ │ └── basic.test.ts.snap │ │ ├── basic.test.ts │ │ ├── importNoodle.ts │ │ ├── support │ │ │ ├── barttest.csv │ │ │ └── sample.csv │ │ └── testUtils.ts │ ├── tsconfig-build.json │ └── tsconfig.json ├── reltab │ ├── jest.config.json │ ├── package.json │ ├── src │ │ ├── AggFn.ts │ │ ├── BaseSQLDialect.ts │ │ ├── ColumnStats.ts │ │ ├── ColumnType.ts │ │ ├── DataSource.ts │ │ ├── FilterExp.ts │ │ ├── QueryExp.ts │ │ ├── QueryRep.ts │ │ ├── SQLQuery.ts │ │ ├── Schema.ts │ │ ├── TableRep.ts │ │ ├── apiUtils.ts │ │ ├── d3utils.ts │ │ ├── defs.ts │ │ ├── dialect.ts │ │ ├── dialectRegistry.ts │ │ ├── dialects │ │ │ ├── BigQueryDialect.ts │ │ │ ├── DuckDBDialect.ts │ │ │ ├── PrestoDialect.ts │ │ │ ├── SQLiteDialect.ts │ │ │ └── SnowflakeDialect.ts │ │ ├── getSchema.ts │ │ ├── histogram.ts │ │ ├── pp.ts │ │ ├── reltab.ts │ │ ├── remote │ │ │ ├── Connection.ts │ │ │ ├── Transport.ts │ │ │ ├── errorUtils.ts │ │ │ ├── result.ts │ │ │ └── server.ts │ │ ├── toSql.ts │ │ └── util │ │ │ └── environ.ts │ ├── test │ │ ├── basic.test.ts │ │ └── serErr.test.ts │ ├── tsconfig-build.json │ └── tsconfig.json ├── tad-app │ ├── app │ │ ├── appMenu.ts │ │ ├── appWindow.ts │ │ ├── fileExport.ts │ │ ├── main.ts │ │ ├── preload.js │ │ ├── quickStart.ts │ │ ├── setup.ts │ │ └── updater.ts │ ├── buildRes │ │ └── icon.icns │ ├── csv │ │ ├── 12345.csv │ │ ├── backslashtest.csv │ │ ├── badfmt.csv │ │ ├── bart-comp-all.csv │ │ ├── barttest-no-headers.csv │ │ ├── barttest.csv │ │ ├── ecsv-example-basic.csv │ │ ├── ecsv-example-noheaders.csv │ │ ├── htmldata.csv │ │ ├── movie-grossByCountryDirector.tad │ │ ├── movie-grossByDirectorWithFilter.tad │ │ ├── movie_metadata.csv │ │ ├── nulltest.csv │ │ ├── nyt-fb-posts-brief.csv │ │ ├── small.csv │ │ ├── smalltxt.csv │ │ ├── tabtest.csv │ │ └── test-issue61.csv │ ├── examples │ │ ├── movie-grossByCountryDirector.tad │ │ ├── movie-grossByDirectorWithFilter.tad │ │ └── movie_metadata.csv │ ├── html │ │ ├── index.html │ │ └── userdocs │ │ │ ├── css │ │ │ ├── bootstrap.min.css │ │ │ └── main.css │ │ │ ├── fonts │ │ │ ├── glyphicons-halflings-regular.eot │ │ │ ├── glyphicons-halflings-regular.svg │ │ │ ├── glyphicons-halflings-regular.ttf │ │ │ ├── glyphicons-halflings-regular.woff │ │ │ └── glyphicons-halflings-regular.woff2 │ │ │ ├── images │ │ │ ├── tad-data-sources.png │ │ │ ├── tad-quickstart-1.png │ │ │ ├── tad-quickstart-2.png │ │ │ ├── tad-quickstart-3.png │ │ │ ├── tad-quickstart-filter-1.png │ │ │ ├── tad-quickstart-filter-2.png │ │ │ └── tad-quickstart-filter-3.png │ │ │ ├── js │ │ │ ├── bootstrap.min.js │ │ │ ├── clipboard.min.js │ │ │ └── jquery.min.js │ │ │ └── quickstart.html │ ├── package.json │ ├── res │ │ ├── AppIcon.icns │ │ ├── AppIcon1024.png │ │ ├── blue_external_drive.icns │ │ └── entitlements.mac.plist │ ├── runtad.sh │ ├── src │ │ ├── app.css │ │ ├── electronClient.ts │ │ ├── electronRenderMain.tsx │ │ └── openParams.ts │ ├── tad.sh │ ├── tools │ │ ├── afterPack.js │ │ ├── convert.sh │ │ ├── dylib-fixup.sh │ │ └── notarize.js │ ├── tsconfig.json │ └── webpack.config.js ├── tadviewer │ ├── html │ │ ├── css │ │ │ ├── bootstrap.min.css │ │ │ └── main.css │ │ ├── fonts │ │ │ ├── glyphicons-halflings-regular.eot │ │ │ ├── glyphicons-halflings-regular.svg │ │ │ ├── glyphicons-halflings-regular.ttf │ │ │ ├── glyphicons-halflings-regular.woff │ │ │ └── glyphicons-halflings-regular.woff2 │ │ ├── images │ │ │ ├── tad-quickstart-1.png │ │ │ ├── tad-quickstart-2.png │ │ │ ├── tad-quickstart-filter-1.png │ │ │ ├── tad-quickstart-filter-2.png │ │ │ └── tad-quickstart-filter-3.png │ │ └── js │ │ │ ├── bootstrap.min.js │ │ │ ├── clipboard.min.js │ │ │ └── jquery.min.js │ ├── less │ │ ├── activityBar.less │ │ ├── columnList.less │ │ ├── columnSelector.less │ │ ├── delayedCalcFooter.less │ │ ├── filterEditor.less │ │ ├── footer.less │ │ ├── modal.less │ │ ├── sidebar.less │ │ ├── singleColumnSelect.less │ │ └── tadviewer.less │ ├── package.json │ ├── src │ │ ├── AppState.ts │ │ ├── FormatOptions.ts │ │ ├── NumFormatOptions.ts │ │ ├── PagedDataView.ts │ │ ├── PivotRequester.ts │ │ ├── QueryView.ts │ │ ├── SimpleDataView.ts │ │ ├── TextFormatOptions.ts │ │ ├── Timer.ts │ │ ├── ViewParams.ts │ │ ├── ViewState.ts │ │ ├── actions.ts │ │ ├── components │ │ │ ├── ActivityBar.tsx │ │ │ ├── AggPanel.tsx │ │ │ ├── AppPane.tsx │ │ │ ├── CellClickData.tsx │ │ │ ├── ColumnList.tsx │ │ │ ├── ColumnRow.tsx │ │ │ ├── ColumnSelector.tsx │ │ │ ├── DataGrid.tsx │ │ │ ├── DataSourceSidebar.tsx │ │ │ ├── DelayedCalcFooter.tsx │ │ │ ├── DisplayOrderPanel.tsx │ │ │ ├── FilterEditor.tsx │ │ │ ├── FilterEditorRow.tsx │ │ │ ├── Footer.tsx │ │ │ ├── FormatPanel.tsx │ │ │ ├── GridPane.tsx │ │ │ ├── IndeterminateCheckbox.tsx │ │ │ ├── LoadingModal.tsx │ │ │ ├── NumFormatPanel.tsx │ │ │ ├── PivotOrderPanel.tsx │ │ │ ├── PivotSidebar.tsx │ │ │ ├── SelectionChangeData.tsx │ │ │ ├── Sidebar.tsx │ │ │ ├── SimpleClipboard.tsx │ │ │ ├── SingleColumnSelect.tsx │ │ │ ├── SortOrderPanel.tsx │ │ │ ├── TadViewerPane.tsx │ │ │ ├── TextFormatPanel.tsx │ │ │ └── defs.tsx │ │ ├── index.ts │ │ ├── paging.ts │ │ ├── slickgrid-es6.d.ts │ │ ├── slickgrid.scss │ │ ├── tadviewer.ts │ │ └── util.ts │ ├── tools │ │ └── convert.sh │ ├── tsconfig.json │ └── webpack.config.js ├── tadweb-app │ ├── html │ │ └── index.html │ ├── package.json │ ├── src │ │ ├── reltabWebClient.ts │ │ └── webRenderMain.tsx │ ├── tsconfig.json │ └── webpack.config.js └── tadweb-server │ ├── package.json │ ├── public │ ├── csv │ │ ├── barttest.csv │ │ ├── movie_metadata.csv │ │ └── sample.csv │ └── tadweb-app │ ├── src │ ├── server.ts │ └── testClient.ts │ └── tsconfig.json └── tools ├── build-all.sh ├── build-base.sh ├── build-component.sh ├── build-embedded.sh ├── buildAll.js ├── buildUtils.sh └── packages.sh /.eslintignore: -------------------------------------------------------------------------------- 1 | flow-typed/**/*.js 2 | webpack.config.js 3 | CSSModule.js.flow 4 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "standard", 3 | "parser": "babel-eslint", 4 | "plugins": [ 5 | "standard", "flowtype", "react" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | .*/node_modules/config-chain/test/* 3 | .*/node_modules/npm/test/fixtures/config/* 4 | .*/node_modules/typechecker/es2015/* 5 | .*/node_modules/electron-packager/test/fixtures/* 6 | .*/node_modules/fbjs/lib/* 7 | less/*.less 8 | 9 | [include] 10 | 11 | [libs] 12 | interfaces/ 13 | 14 | [options] 15 | module.name_mapper.extension='less' -> '/CSSModule.js.flow' 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | buildRes 3 | dist 4 | Tad-darwin-x64 5 | release 6 | node_modules 7 | *.map 8 | .DS_Store 9 | npm-debug.log 10 | .vscode 11 | lerna-debug.log 12 | .idea -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Antony Courtney 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /doc/screenshots/Tad-parquet.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonycourtney/tad/272ffa0092024cd57b82fc813af1f8c6568bc3f0/doc/screenshots/Tad-parquet.gif -------------------------------------------------------------------------------- /doc/screenshots/tad-metobjects-pivoted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonycourtney/tad/272ffa0092024cd57b82fc813af1f8c6568bc3f0/doc/screenshots/tad-metobjects-pivoted.png -------------------------------------------------------------------------------- /doc/screenshots/tad-metobjects-unpivoted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonycourtney/tad/272ffa0092024cd57b82fc813af1f8c6568bc3f0/doc/screenshots/tad-metobjects-unpivoted.png -------------------------------------------------------------------------------- /doc/screenshots/tad-movies-pivoted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonycourtney/tad/272ffa0092024cd57b82fc813af1f8c6568bc3f0/doc/screenshots/tad-movies-pivoted.png -------------------------------------------------------------------------------- /doc/screenshots/tad-snowflake-preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonycourtney/tad/272ffa0092024cd57b82fc813af1f8c6568bc3f0/doc/screenshots/tad-snowflake-preview.png -------------------------------------------------------------------------------- /doc/site/CNAME: -------------------------------------------------------------------------------- 1 | tadviewer.com -------------------------------------------------------------------------------- /doc/site/css/main.css: -------------------------------------------------------------------------------- 1 | body{ 2 | font-family: 'Lato', sans-serif; 3 | color: #35556f; 4 | } 5 | header{ 6 | background-image: -moz-linear-gradient( 80deg, rgb(10,110,191) 0%, rgb(29,139,224) 100%); 7 | background-image: -webkit-linear-gradient( 80deg, rgb(10,110,191) 0%, rgb(29,139,224) 100%); 8 | background-image: -ms-linear-gradient( 80deg, rgb(10,110,191) 0%, rgb(29,139,224) 100%); 9 | color: #FFF; 10 | text-align: center; 11 | padding-bottom: 72px; 12 | } 13 | header .navbar-nav > li > a{ 14 | padding-top: 22px; 15 | } 16 | .navbar-brand img{ 17 | max-width: 70px; 18 | } 19 | h1{ 20 | font-size: 50px; 21 | color: #FFF; 22 | font-weight: 100; 23 | margin-top:100px; 24 | } 25 | h1.ornament{ 26 | background-image: url(../images/ornament.png); 27 | background-repeat: no-repeat; 28 | background-position: bottom center; 29 | padding-bottom: 51px; 30 | } 31 | .screenshot-holder{ 32 | margin-top: 48px; 33 | margin-bottom:44px; 34 | } 35 | .screenshot-holder img{ 36 | max-width: 100%; 37 | box-shadow: 0px 10px 25px 4px rgba(18, 49, 74, 0.25); 38 | } 39 | .btn-default{ 40 | color: #0072ce; 41 | font-size: 22px; 42 | margin-top:10px; 43 | border-radius: 16px; 44 | font-weight: 700; 45 | min-width: 217px; 46 | } 47 | .navbar-brand{ 48 | padding-top: 22px; 49 | font-weight: 600; 50 | font-size: 40px; 51 | } 52 | .features-section { 53 | text-align: center; 54 | font-size: 17px; 55 | padding-bottom: 60px; 56 | } 57 | .features-section p strong{ 58 | color:#0072ce; 59 | } 60 | .features-section .specs-holder img{ 61 | max-width: 55px; 62 | } 63 | .features-section .specs-holder{ 64 | margin-top:60px; 65 | padding: 0 70px; 66 | } 67 | .features-section .specs-holder p{ 68 | margin-top:28px; 69 | line-height: 28px; 70 | } 71 | .release-section { 72 | margin-top: 32px; 73 | } 74 | .download { 75 | text-align: center; 76 | } 77 | footer{ 78 | text-align: center; 79 | } 80 | footer p{ 81 | font-size: 14px; 82 | } 83 | footer .container{ 84 | border-top:1px solid #cce3f5; 85 | padding: 25px 15px; 86 | } 87 | .section .container { 88 | border-top:1px solid #cce3f5; 89 | padding-bottom: 25px; 90 | } 91 | footer p a{ 92 | font-weight: bold; 93 | color:#35556f; 94 | } 95 | @media (max-width:1199px){ 96 | .features-section .specs-holder{ 97 | padding: 0 20px; 98 | } 99 | } 100 | @media (max-width:991px){ 101 | body{ 102 | font-size: 14px; 103 | } 104 | h1{ 105 | font-size: 35px; 106 | } 107 | p{ 108 | font-size:14px; 109 | } 110 | 111 | } 112 | @media (max-width:767px){ 113 | .navbar-header{ 114 | min-height: 60px; 115 | } 116 | .navbar-default .navbar-toggle{ 117 | background: #FFF; 118 | } 119 | .navbar-default .navbar-toggle .icon-bar{ 120 | background: #1d8be0; 121 | } 122 | header .navbar-nav > li > a{ 123 | font-size: 18px; 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /doc/site/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonycourtney/tad/272ffa0092024cd57b82fc813af1f8c6568bc3f0/doc/site/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /doc/site/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonycourtney/tad/272ffa0092024cd57b82fc813af1f8c6568bc3f0/doc/site/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /doc/site/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonycourtney/tad/272ffa0092024cd57b82fc813af1f8c6568bc3f0/doc/site/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /doc/site/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonycourtney/tad/272ffa0092024cd57b82fc813af1f8c6568bc3f0/doc/site/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /doc/site/images/Tad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonycourtney/tad/272ffa0092024cd57b82fc813af1f8c6568bc3f0/doc/site/images/Tad.png -------------------------------------------------------------------------------- /doc/site/images/Tad@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonycourtney/tad/272ffa0092024cd57b82fc813af1f8c6568bc3f0/doc/site/images/Tad@2x.png -------------------------------------------------------------------------------- /doc/site/images/csv-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonycourtney/tad/272ffa0092024cd57b82fc813af1f8c6568bc3f0/doc/site/images/csv-icon.png -------------------------------------------------------------------------------- /doc/site/images/csv-icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonycourtney/tad/272ffa0092024cd57b82fc813af1f8c6568bc3f0/doc/site/images/csv-icon@2x.png -------------------------------------------------------------------------------- /doc/site/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonycourtney/tad/272ffa0092024cd57b82fc813af1f8c6568bc3f0/doc/site/images/favicon.ico -------------------------------------------------------------------------------- /doc/site/images/favicon2.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonycourtney/tad/272ffa0092024cd57b82fc813af1f8c6568bc3f0/doc/site/images/favicon2.ico -------------------------------------------------------------------------------- /doc/site/images/ornament.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonycourtney/tad/272ffa0092024cd57b82fc813af1f8c6568bc3f0/doc/site/images/ornament.png -------------------------------------------------------------------------------- /doc/site/images/ornament@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonycourtney/tad/272ffa0092024cd57b82fc813af1f8c6568bc3f0/doc/site/images/ornament@2x.png -------------------------------------------------------------------------------- /doc/site/images/pivot-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonycourtney/tad/272ffa0092024cd57b82fc813af1f8c6568bc3f0/doc/site/images/pivot-icon.png -------------------------------------------------------------------------------- /doc/site/images/pivot-icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonycourtney/tad/272ffa0092024cd57b82fc813af1f8c6568bc3f0/doc/site/images/pivot-icon@2x.png -------------------------------------------------------------------------------- /doc/site/images/puzzle-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonycourtney/tad/272ffa0092024cd57b82fc813af1f8c6568bc3f0/doc/site/images/puzzle-icon.png -------------------------------------------------------------------------------- /doc/site/images/puzzle-icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonycourtney/tad/272ffa0092024cd57b82fc813af1f8c6568bc3f0/doc/site/images/puzzle-icon@2x.png -------------------------------------------------------------------------------- /doc/site/images/screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonycourtney/tad/272ffa0092024cd57b82fc813af1f8c6568bc3f0/doc/site/images/screenshot.jpg -------------------------------------------------------------------------------- /doc/site/images/tad-site-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonycourtney/tad/272ffa0092024cd57b82fc813af1f8c6568bc3f0/doc/site/images/tad-site-screenshot.png -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": ["packages/*"], 3 | "version": "0.14.0", 4 | "command": { 5 | "run": { 6 | "ignore": ["reltab-sqlite", "tad-app"] 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tad", 3 | "version": "0.14.0", 4 | "description": "Lerna monorepo for Tabular data viewer libraries and apps", 5 | "overrides": { 6 | "ws": "8.18.1" 7 | }, 8 | "devDependencies": { 9 | "@types/jest": "^29.5.14", 10 | "jest": "^29.7.0", 11 | "lerna": "^4.0.0", 12 | "rimraf": "^5.0.5", 13 | "run-script-os": "^1.1.6", 14 | "ts-jest": "^29.2.6", 15 | "typescript": "^4.8.3" 16 | }, 17 | "scripts": { 18 | "bootstrap": "lerna bootstrap --ci --hoist --force-local --ignore-prepublish", 19 | "bootstrap-no-drivers-no-app": "lerna bootstrap --ignore reltab-* --ignore tad --ci --hoist --force-local --ignore-prepublish" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/aggtree/README.md: -------------------------------------------------------------------------------- 1 | # `aggtree` 2 | 3 | > TODO: description 4 | 5 | ## Usage 6 | 7 | ``` 8 | const aggtree = require('aggtree'); 9 | 10 | // TODO: DEMONSTRATE API 11 | ``` 12 | -------------------------------------------------------------------------------- /packages/aggtree/jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "transform": { 3 | "^.+\\.tsx?$": "ts-jest" 4 | }, 5 | "testRegex": "/test/.*test.ts", 6 | "testEnvironment": "node", 7 | "testPathIgnorePatterns": ["/node_modules/", "/__snapshots__"], 8 | "moduleFileExtensions": ["ts", "js"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/aggtree/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aggtree", 3 | "version": "0.14.0", 4 | "description": "pivot table built on top of reltab", 5 | "author": "Antony Courtney ", 6 | "homepage": "https://github.com/antonycourtney/tad#readme", 7 | "license": "MIT", 8 | "main": "dist/aggtree.js", 9 | "types": "dist/aggtree.d.ts", 10 | "scripts": { 11 | "prepublish": "npm run build", 12 | "clean": "rimraf dist", 13 | "build": "tsc -p tsconfig-build.json", 14 | "test": "jest --config jest.config.json --no-cache" 15 | }, 16 | "dependencies": { 17 | "lodash": "^4.17.21", 18 | "loglevel": "^1.8.0" 19 | }, 20 | "devDependencies": { 21 | "@types/jest": "^29.5.14", 22 | "@types/node": "^18.7.18", 23 | "jest": "^29.7.0", 24 | "mkdirp": "^1.0.4", 25 | "prettier": "^2.5.1", 26 | "reltab": "^0.12.0", 27 | "reltab-sqlite": "^0.12.0", 28 | "ts-jest": "^29.2.6", 29 | "typescript": "^4.8.3" 30 | }, 31 | "peerDependencies": { 32 | "reltab": "^0.12.0", 33 | "reltab-sqlite": "^0.12.0" 34 | }, 35 | "repository": { 36 | "type": "git", 37 | "url": "git+https://github.com/antonycourtney/tad.git" 38 | }, 39 | "bugs": { 40 | "url": "https://github.com/antonycourtney/tad/issues" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/aggtree/test/pathTree.test.ts: -------------------------------------------------------------------------------- 1 | import * as sqlite3 from "sqlite3"; 2 | import * as reltab from "reltab"; 3 | import * as aggtree from "../src/aggtree"; 4 | import { PathTree } from "../src/PathTree"; 5 | import * as reltabSqlite from "reltab-sqlite"; 6 | import { textSpanContainsPosition, textSpanContainsTextSpan } from "typescript"; 7 | import { delimiter } from "path"; 8 | import * as log from "loglevel"; 9 | import * as util from "./testUtils"; 10 | 11 | test("basic pathTree", () => { 12 | const pt0 = new PathTree(); 13 | 14 | const pt1 = pt0.open(["foo", "bar", "baz"]); 15 | 16 | log.debug("pt1: ", pt1._rep); 17 | 18 | const pt2 = pt1.open(["foo", "bar", "blech"]).open(["a", "b", "c"]); 19 | 20 | log.debug("pt2: ", pt2._rep); 21 | 22 | for (let path of pt2.iter()) { 23 | log.debug(path); 24 | } 25 | 26 | const pt2Paths = Array.from(pt2.iter()); 27 | log.debug("pt2Paths: ", pt2Paths); 28 | expect(pt2Paths).toMatchInlineSnapshot(` 29 | Array [ 30 | Array [ 31 | "foo", 32 | ], 33 | Array [ 34 | "foo", 35 | "bar", 36 | ], 37 | Array [ 38 | "foo", 39 | "bar", 40 | "baz", 41 | ], 42 | Array [ 43 | "foo", 44 | "bar", 45 | "blech", 46 | ], 47 | Array [ 48 | "a", 49 | ], 50 | Array [ 51 | "a", 52 | "b", 53 | ], 54 | Array [ 55 | "a", 56 | "b", 57 | "c", 58 | ], 59 | ] 60 | `); 61 | }); 62 | -------------------------------------------------------------------------------- /packages/aggtree/test/testUtils.ts: -------------------------------------------------------------------------------- 1 | import * as reltab from "reltab"; 2 | export const columnSum = ( 3 | tableData: reltab.TableRep, 4 | columnId: string 5 | ): number => { 6 | var sum: number = 0; 7 | 8 | for (var i = 0; i < tableData.rowData.length; i++) { 9 | sum += tableData.rowData[i][columnId] as number; 10 | } 11 | return sum; 12 | }; 13 | 14 | type Handler = (err: any) => void; 15 | 16 | export const mkAsyncErrHandler = (t: any, msg: string): Handler => { 17 | return (err) => { 18 | console.error("caught async promise exception: ", err.stack); 19 | t.fail(msg + ": " + err); 20 | }; 21 | }; 22 | 23 | interface LogTableOptions { 24 | maxRows?: number; 25 | } 26 | 27 | export const logTable = ( 28 | table: reltab.TableRep, 29 | options: LogTableOptions | null = null 30 | ): void => { 31 | // Node's console-table package has slightly different synopsis 32 | // than browser version; accepts column names as first arg: 33 | const ctf: any = console.table; 34 | 35 | const rowData = 36 | options && options.maxRows 37 | ? table.rowData.slice(0, options.maxRows) 38 | : table.rowData; 39 | 40 | // ctf(table.schema.columns, rowData); 41 | ctf(rowData); 42 | }; 43 | -------------------------------------------------------------------------------- /packages/aggtree/tsconfig-build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["./test/*"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/aggtree/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist", 4 | "downlevelIteration": true, 5 | "declaration": true, 6 | "strict": true, 7 | "target": "ES5", 8 | "lib": ["es6", "es2017", "dom"] 9 | }, 10 | "include": ["./src/*", "./test/*"] 11 | } 12 | -------------------------------------------------------------------------------- /packages/reltab-aws-athena/jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "transform": { 3 | "^.+\\.tsx?$": "ts-jest" 4 | }, 5 | "testRegex": "/test/.*test.ts", 6 | "testEnvironment": "node", 7 | "testPathIgnorePatterns": ["/node_modules/", "/__snapshots__"], 8 | "moduleFileExtensions": ["ts", "js"], 9 | "testTimeout": 30000 10 | } 11 | -------------------------------------------------------------------------------- /packages/reltab-aws-athena/noodle/ambJoinTest.sql: -------------------------------------------------------------------------------- 1 | SELECT t1."country", "sum_gross", "director_name", "movie_title" 2 | FROM 3 | (SELECT "country", 4 | sum("gross") AS "sum_gross" 5 | FROM "imdb_top_rated" 6 | GROUP BY "country") t1 LEFT OUTER 7 | JOIN 8 | (SELECT "country", 9 | "director_name", 10 | "movie_title" 11 | FROM "imdb_top_rated") t2 12 | USING ("country") -------------------------------------------------------------------------------- /packages/reltab-aws-athena/noodle/joinTest.sql: -------------------------------------------------------------------------------- 1 | SELECT "_path0", "_sortVal_0_0", "director_name", "movie_title" 2 | FROM 3 | (SELECT "country" AS "_path0", 4 | sum("title_year") AS "_sortVal_0_0", 5 | sum("gross") AS "_sortVal_0_1" 6 | FROM "imdb_top_rated" 7 | GROUP BY "country") t1 LEFT OUTER 8 | JOIN 9 | (SELECT "country" AS "_path0", 10 | "director_name", 11 | "movie_title" 12 | FROM "imdb_top_rated") t2 13 | USING ("_path0") -------------------------------------------------------------------------------- /packages/reltab-aws-athena/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reltab-aws-athena", 3 | "version": "0.14.0", 4 | "description": "AWS Athena driver for reltab", 5 | "main": "dist/reltab-aws-athena.js", 6 | "types": "dist/reltab-aws-athena.d.ts", 7 | "scripts": { 8 | "prepublish": "npm run build", 9 | "clean": "rimraf dist", 10 | "build": "tsc", 11 | "test": "jest --config jest.config.json --no-cache", 12 | "test-resnap": "jest --config jest.config.json --updateSnapshot" 13 | }, 14 | "keywords": [ 15 | "relational", 16 | "sql", 17 | "database", 18 | "aws", 19 | "athena", 20 | "presto" 21 | ], 22 | "author": "Antony Courtney ", 23 | "license": "MIT", 24 | "dependencies": { 25 | "athena-express": "^7.1.2", 26 | "aws-sdk": "^2.1050.0", 27 | "loglevel": "^1.8.0" 28 | }, 29 | "devDependencies": { 30 | "@types/jest": "^29.5.14", 31 | "@types/node": "^18.7.18", 32 | "aggtree": "^0.12.0", 33 | "console.table": "^0.10.0", 34 | "jest": "^29.7.0", 35 | "mkdirp": "^1.0.4", 36 | "prettier": "^2.5.1", 37 | "reltab": "^0.12.0", 38 | "ts-jest": "^29.2.6", 39 | "typescript": "^4.8.3" 40 | }, 41 | "peerDependencies": { 42 | "aggtree": "^0.12.0", 43 | "reltab": "^0.12.0" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/reltab-aws-athena/test/testUtils.ts: -------------------------------------------------------------------------------- 1 | import * as reltab from "reltab"; 2 | 3 | export const columnSum = ( 4 | tableData: reltab.TableRep, 5 | columnId: string 6 | ): number => { 7 | var sum: number = 0; 8 | 9 | for (var i = 0; i < tableData.rowData.length; i++) { 10 | sum += tableData.rowData[i][columnId] as number; 11 | } 12 | return sum; 13 | }; 14 | 15 | type Handler = (err: any) => void; 16 | 17 | export const mkAsyncErrHandler = (t: any, msg: string): Handler => { 18 | return (err) => { 19 | console.error("caught async promise exception: ", err.stack); 20 | t.fail(msg + ": " + err); 21 | }; 22 | }; 23 | 24 | interface LogTableOptions { 25 | maxRows?: number; 26 | } 27 | 28 | export const logTable = ( 29 | table: reltab.TableRep, 30 | options: LogTableOptions | null = null 31 | ): void => { 32 | // Node's console-table package has slightly different synopsis 33 | // than browser version; accepts column names as first arg: 34 | const ctf: any = console.table; 35 | 36 | const rowData = 37 | options && options.maxRows 38 | ? table.rowData.slice(0, options.maxRows) 39 | : table.rowData; 40 | 41 | ctf(rowData); 42 | }; 43 | -------------------------------------------------------------------------------- /packages/reltab-aws-athena/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist", 4 | "declaration": true, 5 | "target": "ES5", 6 | "strict": true, 7 | "lib": ["es6", "es2017", "dom"] 8 | }, 9 | "include": ["./src/*"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/reltab-aws-athena/util/create_movie_metadata.sql: -------------------------------------------------------------------------------- 1 | CREATE EXTERNAL TABLE `movie_metadata`( 2 | `color` string, 3 | `director_name` string, 4 | `num_critic_for_reviews` int, 5 | `duration` int, 6 | `director_facebook_likes` int, 7 | `actor_3_facebook_likes` int, 8 | `actor_2_name` string, 9 | `actor_1_facebook_likes` int, 10 | `gross` int, 11 | `genres` string, 12 | `actor_1_name` string, 13 | `movie_title` string, 14 | `num_voted_users` int, 15 | `cast_total_facebook_likes` int, 16 | `actor_3_name` string, 17 | `facenumber_in_poster` int, 18 | `plot_keywords` string, 19 | `movie_imdb_link` string, 20 | `num_user_for_reviews` int, 21 | `language` string, 22 | `country` string, 23 | `content_rating` string, 24 | `budget` int, 25 | `title_year` int, 26 | `actor_2_facebook_likes` int, 27 | `imdb_score` double, 28 | `aspect_ratio` double, 29 | `movie_facebook_likes` int) 30 | ROW FORMAT DELIMITED 31 | FIELDS TERMINATED BY ',' 32 | STORED AS INPUTFORMAT 33 | 'org.apache.hadoop.mapred.TextInputFormat' 34 | OUTPUTFORMAT 35 | 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat' 36 | LOCATION 37 | 's3://ac-athena-test-data/uploads' 38 | TBLPROPERTIES ( 39 | 'has_encrypted_data'='false', 40 | 'transient_lastDdlTime'='1590519457', 41 | 'skip.header.line.count'='1') -------------------------------------------------------------------------------- /packages/reltab-bigquery/jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "transform": { 3 | "^.+\\.tsx?$": "ts-jest" 4 | }, 5 | "testRegex": "/test/.*test.ts", 6 | "testEnvironment": "node", 7 | "testPathIgnorePatterns": ["/node_modules/", "/__snapshots__"], 8 | "moduleFileExtensions": ["ts", "js"], 9 | "testTimeout": 30000 10 | } 11 | -------------------------------------------------------------------------------- /packages/reltab-bigquery/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reltab-bigquery", 3 | "version": "0.14.0", 4 | "description": "Google BigQuery driver for reltab", 5 | "main": "dist/reltab-bigquery.js", 6 | "types": "dist/reltab-bigquery.d.ts", 7 | "scripts": { 8 | "prepublish": "npm run build", 9 | "clean": "rimraf dist", 10 | "build": "tsc", 11 | "test": "jest --config jest.config.json --no-cache", 12 | "test-resnap": "jest --config jest.config.json --updateSnapshot" 13 | }, 14 | "keywords": [ 15 | "relational", 16 | "sql", 17 | "database", 18 | "bigquery" 19 | ], 20 | "author": "Antony Courtney ", 21 | "license": "MIT", 22 | "dependencies": { 23 | "@google-cloud/bigquery": "5.9.3", 24 | "loglevel": "^1.8.0" 25 | }, 26 | "devDependencies": { 27 | "@types/jest": "^29.5.14", 28 | "@types/node": "^18.7.18", 29 | "aggtree": "^0.12.0", 30 | "console.table": "^0.10.0", 31 | "jest": "^29.7.0", 32 | "mkdirp": "^1.0.4", 33 | "prettier": "^2.5.1", 34 | "reltab": "^0.12.0", 35 | "ts-jest": "^29.2.6", 36 | "typescript": "^4.8.3" 37 | }, 38 | "peerDependencies": { 39 | "aggtree": "^0.12.0", 40 | "reltab": "^0.12.0" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/reltab-bigquery/test/testUtils.ts: -------------------------------------------------------------------------------- 1 | import * as reltab from "reltab"; 2 | 3 | export const columnSum = ( 4 | tableData: reltab.TableRep, 5 | columnId: string 6 | ): number => { 7 | var sum: number = 0; 8 | 9 | for (var i = 0; i < tableData.rowData.length; i++) { 10 | sum += tableData.rowData[i][columnId] as number; 11 | } 12 | return sum; 13 | }; 14 | 15 | type Handler = (err: any) => void; 16 | 17 | export const mkAsyncErrHandler = (t: any, msg: string): Handler => { 18 | return (err) => { 19 | console.error("caught async promise exception: ", err.stack); 20 | t.fail(msg + ": " + err); 21 | }; 22 | }; 23 | 24 | interface LogTableOptions { 25 | maxRows?: number; 26 | } 27 | 28 | export const logTable = ( 29 | table: reltab.TableRep, 30 | options: LogTableOptions | null = null 31 | ): void => { 32 | // Node's console-table package has slightly different synopsis 33 | // than browser version; accepts column names as first arg: 34 | const ctf: any = console.table; 35 | 36 | const rowData = 37 | options && options.maxRows 38 | ? table.rowData.slice(0, options.maxRows) 39 | : table.rowData; 40 | 41 | ctf(rowData); 42 | }; 43 | -------------------------------------------------------------------------------- /packages/reltab-bigquery/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist", 4 | "declaration": true, 5 | "target": "ES5", 6 | "strict": true, 7 | "lib": ["es6", "es2017", "dom"] 8 | }, 9 | "include": ["./src/*"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/reltab-duckdb/jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "transform": { 3 | "^.+\\.tsx?$": "ts-jest" 4 | }, 5 | "testRegex": "/test/.*test.ts", 6 | "testEnvironment": "node", 7 | "testPathIgnorePatterns": ["/node_modules/", "/__snapshots__"], 8 | "moduleFileExtensions": ["ts", "js"], 9 | "testTimeout": 15000 10 | } 11 | -------------------------------------------------------------------------------- /packages/reltab-duckdb/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reltab-duckdb", 3 | "version": "0.14.0", 4 | "description": "sqlite driver for reltab", 5 | "main": "dist/reltab-duckdb.js", 6 | "types": "dist/reltab-duckdb.d.ts", 7 | "scripts": { 8 | "prepublish": "npm run build", 9 | "clean": "rimraf dist", 10 | "build": "tsc -p tsconfig-build.json", 11 | "test": "AWS_PROFILE=dev ./scripts/run-tests-locally.sh", 12 | "test:run": "jest --config jest.config.json --no-cache" 13 | }, 14 | "keywords": [ 15 | "relational", 16 | "sql", 17 | "database", 18 | "sqlite" 19 | ], 20 | "author": "Antony Courtney ", 21 | "license": "MIT", 22 | "dependencies": { 23 | "duckdb-async": "^1.0.0", 24 | "loglevel": "^1.8.0", 25 | "pretty-hrtime": "^1.0.3" 26 | }, 27 | "devDependencies": { 28 | "@types/jest": "^29.5.14", 29 | "@types/node": "^18.7.18", 30 | "jest": "^29.7.0", 31 | "mkdirp": "^1.0.4", 32 | "prettier": "^2.5.1", 33 | "reltab": "^0.12.0", 34 | "ts-jest": "^29.2.6", 35 | "typescript": "^4.8.3" 36 | }, 37 | "peerDependencies": { 38 | "reltab": "^0.12.0" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /packages/reltab-duckdb/scripts/run-tests-locally.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | export AWS_REGION="us-east-1" 5 | export AWS_ACCESS_KEY_ID=$(aws configure get aws_access_key_id --profile $AWS_PROFILE) 6 | export AWS_SECRET_ACCESS_KEY=$(aws configure get aws_secret_access_key --profile $AWS_PROFILE) 7 | export AWS_SESSION_TOKEN=$(aws configure get aws_session_token --profile $AWS_PROFILE) 8 | 9 | npm run test:run -- $@ 10 | -------------------------------------------------------------------------------- /packages/reltab-duckdb/src/s3utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Support routines for using s3 URLs with DuckDb. 3 | */ 4 | import { Connection, Database } from "duckdb-async"; 5 | import * as log from "loglevel"; 6 | 7 | /** 8 | * Initialize S3 variables on a DuckDb connection from env vars 9 | * It's a bit unfortunate to do this every time we create a DuckDb 10 | * connection, but the s3fs extension depends on these vars being 11 | * set. Hopefully minimal overhead, and we skip if env vars 12 | * not set. 13 | */ 14 | const AWS_DEFAULT_REGION = "us-west-1"; 15 | 16 | export async function initS3(dbConn: Connection) { 17 | const aws_access_key_id = process.env.AWS_ACCESS_KEY_ID; 18 | if (aws_access_key_id && aws_access_key_id.length > 0) { 19 | log.debug("AWS_ACCESS_KEY_ID env var found, initializing S3 vars"); 20 | const s3_region = process.env.AWS_REGION ?? AWS_DEFAULT_REGION; 21 | await dbConn.exec(`SET s3_region='${s3_region}'`); 22 | await dbConn.exec(`SET s3_access_key_id='${aws_access_key_id}'`); 23 | await dbConn.exec( 24 | `SET s3_secret_access_key='${process.env.AWS_SECRET_ACCESS_KEY}'` 25 | ); 26 | log.debug("initS3: done."); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/reltab-duckdb/test/__snapshots__/histo.auto.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`basic column stats and bin count 1`] = ` 4 | Object { 5 | "approxUnique": 23, 6 | "count": 23, 7 | "max": 419661, 8 | "min": 104260, 9 | "pctNull": 0, 10 | "statsType": "numeric", 11 | } 12 | `; 13 | 14 | exports[`full histogram query for all column 1`] = ` 15 | "SELECT \\"column\\", \\"bin\\", count(\\"binCount\\") as \\"binCount\\" 16 | FROM ( 17 | SELECT 'Base' as \\"column\\", CAST(floor(((CAST(\\"Base\\" AS DOUBLE) - CAST(50000 AS DOUBLE)) / CAST(42857.142857142855 AS DOUBLE))) AS INTEGER) as \\"bin\\", 1 as \\"binCount\\" 18 | FROM barttest 19 | ) 20 | GROUP BY \\"column\\", \\"bin\\" 21 | UNION ALL 22 | SELECT \\"column\\", \\"bin\\", count(\\"binCount\\") as \\"binCount\\" 23 | FROM ( 24 | SELECT 'TCOE' as \\"column\\", CAST(floor(((CAST(\\"TCOE\\" AS DOUBLE) - CAST(100000 AS DOUBLE)) / CAST(50000 AS DOUBLE))) AS INTEGER) as \\"bin\\", 1 as \\"binCount\\" 25 | FROM barttest 26 | ) 27 | GROUP BY \\"column\\", \\"bin\\" 28 | " 29 | `; 30 | 31 | exports[`histogram query for column 1`] = ` 32 | "SELECT \\"column\\", \\"bin\\", count(\\"binCount\\") as \\"binCount\\" 33 | FROM ( 34 | SELECT 'TCOE' as \\"column\\", CAST(floor(((CAST(\\"TCOE\\" AS DOUBLE) - CAST(100000 AS DOUBLE)) / CAST(50000 AS DOUBLE))) AS INTEGER) as \\"bin\\", 1 as \\"binCount\\" 35 | FROM barttest 36 | ) 37 | GROUP BY \\"column\\", \\"bin\\" 38 | " 39 | `; 40 | 41 | exports[`histogram query for column 2`] = ` 42 | Object { 43 | "binCount": 7, 44 | "binData": Array [ 45 | 10, 46 | 1, 47 | 6, 48 | 3, 49 | 1, 50 | 1, 51 | 1, 52 | ], 53 | "binWidth": 50000, 54 | "brushMaxVal": 500000, 55 | "brushMinVal": 100000, 56 | "colId": "TCOE", 57 | "niceMaxVal": 450000, 58 | "niceMinVal": 100000, 59 | } 60 | `; 61 | -------------------------------------------------------------------------------- /packages/reltab-duckdb/test/histo.auto.test.ts: -------------------------------------------------------------------------------- 1 | import * as duckdb from "duckdb-async"; 2 | import * as _ from "lodash"; 3 | import * as reltab from "reltab"; 4 | import { 5 | asString, 6 | DataSourceConnection, 7 | DbDataSource, 8 | DuckDBDialect, 9 | tableQuery, 10 | binsForColumn, 11 | columnHistogramQuery, 12 | getNumericColumnHistogramData, 13 | } from "reltab"; 14 | import * as reltabDuckDB from "../src/reltab-duckdb"; 15 | import * as util from "./testUtils"; 16 | import { getFormattedRows } from "./testUtils"; 17 | 18 | const { col, constVal } = reltab; 19 | 20 | const coreTypes = reltab.SQLiteDialect.coreColumnTypes; 21 | 22 | let testCtx: DbDataSource; 23 | 24 | const q1 = reltab.tableQuery("barttest"); 25 | 26 | beforeAll(async (): Promise => { 27 | const ctx = await reltab.getConnection({ 28 | providerName: "duckdb", 29 | resourceId: ":memory:", 30 | }); 31 | 32 | testCtx = ctx as DbDataSource; 33 | 34 | const dbds = ctx as DbDataSource; 35 | const duckDbDriver = dbds.db as reltabDuckDB.DuckDBDriver; 36 | 37 | await reltabDuckDB.nativeCSVImport( 38 | duckDbDriver.db, 39 | "test/support/sample.csv" 40 | ); 41 | await reltabDuckDB.nativeCSVImport( 42 | duckDbDriver.db, 43 | "test/support/barttest.csv" 44 | ); 45 | 46 | return testCtx; 47 | }); 48 | 49 | test("basic column stats and bin count", async () => { 50 | const qres = await testCtx.evalQuery(q1); 51 | 52 | const statsMap = await testCtx.getColumnStatsMap(q1); 53 | 54 | const tcoeColumnStats = statsMap["TCOE"] as reltab.NumericSummaryStats; 55 | 56 | expect(tcoeColumnStats).toMatchSnapshot(); 57 | 58 | const binCount = binsForColumn(tcoeColumnStats); 59 | 60 | console.log("*** number of histogram bins: ", binCount); 61 | }); 62 | 63 | const intType = DuckDBDialect.columnTypes["INTEGER"]; 64 | 65 | test("histogram query for column", async () => { 66 | const schema = await testCtx.getSchema(q1); 67 | const qres = await testCtx.evalQuery(q1); 68 | 69 | const statsMap = await testCtx.getColumnStatsMap(q1); 70 | 71 | const tcoeColumnStats = statsMap["TCOE"] as reltab.NumericSummaryStats; 72 | 73 | const histoInfo = columnHistogramQuery(q1, "TCOE", intType, tcoeColumnStats); 74 | 75 | console.log("histoInfo: ", histoInfo); 76 | 77 | const dbds = testCtx as DbDataSource; 78 | 79 | console.log("*** histogram query: ", histoInfo!.histoQuery.toJS()); 80 | 81 | const sqlQuery = await dbds.getSqlForQuery(histoInfo!.histoQuery); 82 | console.log("**** histogram sql query: \n", sqlQuery); 83 | 84 | expect(sqlQuery).toMatchSnapshot(); 85 | 86 | const histoRes = await testCtx.evalQuery(histoInfo!.histoQuery); 87 | util.logTable(histoRes); 88 | 89 | const histoData = getNumericColumnHistogramData("TCOE", histoInfo!, histoRes); 90 | console.log("histogram data:", histoData); 91 | expect(histoData).toMatchSnapshot(); 92 | }); 93 | 94 | test("full histogram query for all column", async () => { 95 | const schema = await testCtx.getSchema(q1); 96 | 97 | const statsMap = await testCtx.getColumnStatsMap(q1); 98 | 99 | const [histoInfos, histoQuery] = reltab.getColumnHistogramMapQuery( 100 | testCtx, 101 | q1, 102 | schema, 103 | statsMap 104 | ); 105 | 106 | console.log("full histogram infos: ", histoInfos); 107 | const dbds = testCtx as DbDataSource; 108 | 109 | const sqlQuery = await dbds.getSqlForQuery(histoQuery!); 110 | console.log("full histogram query:\n", sqlQuery); 111 | 112 | expect(sqlQuery).toMatchSnapshot(); 113 | }); 114 | -------------------------------------------------------------------------------- /packages/reltab-duckdb/test/ipfs.test.ts: -------------------------------------------------------------------------------- 1 | import * as duckdb from "duckdb-async"; 2 | import * as reltab from "reltab"; 3 | import * as reltabDuckDB from "../src/reltab-duckdb"; 4 | import * as tp from "typed-promisify"; 5 | import { textSpanContainsPosition, textSpanContainsTextSpan } from "typescript"; 6 | import { delimiter } from "path"; 7 | import * as log from "loglevel"; 8 | import * as util from "./testUtils"; 9 | import * as _ from "lodash"; 10 | import { 11 | asString, 12 | DataSourceConnection, 13 | DbDataSource, 14 | Row, 15 | Schema, 16 | tableQuery, 17 | TableRep, 18 | } from "reltab"; 19 | import { getFormattedRows } from "./testUtils"; 20 | 21 | let testCtx: DataSourceConnection; 22 | 23 | beforeAll(async (): Promise => { 24 | const ctx = await reltab.getConnection({ 25 | providerName: "duckdb", 26 | resourceId: ":memory:", 27 | }); 28 | 29 | testCtx = ctx as DataSourceConnection; 30 | 31 | return testCtx; 32 | }); 33 | 34 | const importParquet = async ( 35 | db: duckdb.Database, 36 | path: string 37 | ): Promise => { 38 | const tableName = await reltabDuckDB.nativeParquetImport(db, path); 39 | return tableName; 40 | }; 41 | 42 | test.skip("https import test", async () => { 43 | const dbds = testCtx as DbDataSource; 44 | const driver = dbds.db as reltabDuckDB.DuckDBDriver; 45 | 46 | const tableName = await importParquet( 47 | driver.db, 48 | "https://github.com/deepcrawl/node-duckdb/raw/master/src/tests/test-fixtures/alltypes_plain.parquet" 49 | ); 50 | 51 | // console.log("https import complete, tableName: ", tableName); 52 | 53 | const q1 = reltab.tableQuery(tableName); 54 | const qres = await testCtx.evalQuery(q1); 55 | // console.log("basic tableQuery result: ", qres); 56 | 57 | const q2 = q1.project([ 58 | "id", 59 | "bool_col", 60 | "int_col", 61 | "string_col", 62 | "timestamp_col", 63 | ]); 64 | const q2res = await testCtx.evalQuery(q2); 65 | 66 | const q2sres = JSON.stringify(q2res, null, 2); 67 | 68 | // console.log("project query result: ", q2sres ); 69 | expect(q2sres).toMatchSnapshot(); 70 | 71 | const fmtRows = getFormattedRows(qres); 72 | // console.log("fmtRows: ", fmtRows); 73 | expect(fmtRows).toMatchSnapshot(); 74 | }); 75 | 76 | test.skip("s3 import test", async () => { 77 | const dbc = testCtx; 78 | const dbds = testCtx as DbDataSource; 79 | const driver = dbds.db as reltabDuckDB.DuckDBDriver; 80 | 81 | let importSucceeded = false; 82 | let tableName: string = ""; 83 | // const s3URL = 's3://ursa-labs-taxi-data/2009/01/data.parquet'; 84 | const s3URL = 85 | "s3://amazon-reviews-pds/parquet/product_category=Books/part-00000-495c48e6-96d6-4650-aa65-3c36a3516ddd.c000.snappy.parquet"; 86 | try { 87 | tableName = await importParquet(driver.db, s3URL); 88 | console.log("s3 import complete, tableName: ", tableName); 89 | importSucceeded = true; 90 | } catch (err) { 91 | console.error("caught error during s3 import: ", err); 92 | } 93 | expect(importSucceeded).toBe(true); 94 | }, 15000); 95 | -------------------------------------------------------------------------------- /packages/reltab-duckdb/test/support/barttest.csv: -------------------------------------------------------------------------------- 1 | Name,Title,Base,TCOE,Job Family,Union 2 | "Crunican, Grace",General Manager,312461,399921,Executive Management,Non-Represented 3 | "Dugger, Dorothy",General Manager,298700,419661,Executive Management,Non-Represented 4 | "Burrows, Matthew",General Counsel,238269,321543,Legal & Paralegal,Non-Represented 5 | "Oversier, Paul",Assistant General Manager Operations,212647,288160,Executive Management,Non-Represented 6 | "Schroeder, Scott",Controller-Treasurer,208744,287139,Finance & Accounting,Non-Represented 7 | "DeVaughn, Marcia",Deputy General Manager,206914,281548,Executive Management,Non-Represented 8 | "Crespo, Rodolfo",Chief Transportation Officer,170383,238211,Transportation Operations,Non-Represented 9 | "Lee, Thomas",Senior Attorney,165470,224345,Legal & Paralegal,Non-Represented 10 | "Nuetzel, Victoria",Senior Attorney,160289,220683,Legal & Paralegal,Non-Represented 11 | "Ruffa, Frank","Group Manager, Capital Projects",157300,209709,Engineering & Systems Engineering,Non-Represented 12 | "Dupont, Jean-Luc",Group Manager Systems Capital Prog,152434,212535,Engineering & Systems Engineering,Non-Represented 13 | "Hardy, Leonard",Chief Safety Officer,152125,211941,Safety,Non-Represented 14 | "Hui, Sam",Wayside Inspector,77446,128332,"Maintenance, Vehicle & Facilities",SEIU 15 | "Reinhardt, Michael",Structures Inspector,77446,124962,"Maintenance, Vehicle & Facilities",SEIU 16 | "Estrada, Phillip",Vehicle Inspector,77446,123432,"Maintenance, Vehicle & Facilities",SEIU 17 | "Ornellas, Robert",Revenue Protection Guard,75701,127660,Police,BPOA 18 | "Smith, Daniel",Transit Vehicle Mechanic,74068,104260,"Maintenance, Vehicle & Facilities",SEIU 19 | "Smith, Westley",Train Operator,61184,118799,Transportation Operations,ATU 20 | "Hamill, Kerry",Department Manager Gov't & Comm Rel,130035,181955,Public Affairs & Marketing,Non-Represented 21 | "Serrianni, Cheryl",Station Agent,60790,132030,Transportation Operations,ATU 22 | "Nagel, Kevin",Station Agent,60781,105854,Transportation Operations,ATU 23 | "Riggs, Joshua",Station Agent,60771,115104,Transportation Operations,ATU 24 | "Knight, Joshua",Electrician,59817,113775,"Maintenance, Vehicle & Facilities",SEIU 25 | -------------------------------------------------------------------------------- /packages/reltab-duckdb/test/support/sample.csv: -------------------------------------------------------------------------------- 1 | firstName,lastName,email,phoneNumber 2 | John,Doe,john@doe.com,0123456789 3 | Jane,Doe,jane@doe.com,9876543210 4 | James,Bond,james.bond@mi6.co.uk,0612345678 -------------------------------------------------------------------------------- /packages/reltab-duckdb/test/testUtils.ts: -------------------------------------------------------------------------------- 1 | import * as reltab from "reltab"; 2 | import { Row, Schema, TableRep } from "reltab"; 3 | 4 | export const columnSum = ( 5 | tableData: reltab.TableRep, 6 | columnId: string 7 | ): number => { 8 | var sum: number; 9 | 10 | if (tableData.rowData.length > 0) { 11 | sum = tableData.rowData[0][columnId] as number; 12 | } else { 13 | sum = 0; 14 | } 15 | 16 | for (var i = 1; i < tableData.rowData.length; i++) { 17 | sum += tableData.rowData[i][columnId] as number; 18 | } 19 | return sum; 20 | }; 21 | 22 | type Handler = (err: any) => void; 23 | 24 | export const mkAsyncErrHandler = (t: any, msg: string): Handler => { 25 | return (err) => { 26 | console.error("caught async promise exception: ", err.stack); 27 | t.fail(msg + ": " + err); 28 | }; 29 | }; 30 | 31 | interface LogTableOptions { 32 | maxRows?: number; 33 | } 34 | 35 | export const logTable = ( 36 | table: reltab.TableRep, 37 | options: LogTableOptions | null = null 38 | ): void => { 39 | // Node's console-table package has slightly different synopsis 40 | // than browser version; accepts column names as first arg: 41 | const ctf: any = console.table; 42 | 43 | const rowData = 44 | options && options.maxRows 45 | ? table.rowData.slice(0, options.maxRows) 46 | : table.rowData; 47 | 48 | ctf(rowData); 49 | }; 50 | 51 | type RowFormatter = (row: Row) => string[]; 52 | 53 | function mkRowFormatter(s: Schema): RowFormatter { 54 | const fmtRow = (r: Row): string[] => { 55 | const res = s.columns.map((cid: string) => { 56 | const val = r[cid]; 57 | const ct = s.columnType(cid); 58 | return ct.stringRender(val); 59 | }); 60 | return res; 61 | }; 62 | return fmtRow; 63 | } 64 | 65 | export function getFormattedRows(qres: TableRep): string[][] { 66 | const s: Schema = qres.schema; 67 | const rowFormatter = mkRowFormatter(s); 68 | 69 | return qres.rowData.map(rowFormatter); 70 | } 71 | -------------------------------------------------------------------------------- /packages/reltab-duckdb/tsconfig-build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["./test/*"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/reltab-duckdb/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist", 4 | "declaration": true, 5 | "module": "CommonJS", 6 | "sourceMap": true, 7 | "target": "ES2015", 8 | "strict": true, 9 | "lib": ["es6", "es2017", "dom"] 10 | }, 11 | "include": ["./src/*", "./test/*.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /packages/reltab-fs/jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "transform": { 3 | "^.+\\.tsx?$": "ts-jest" 4 | }, 5 | "testRegex": "/test/.*test.ts", 6 | "testEnvironment": "node", 7 | "testPathIgnorePatterns": [ 8 | "/node_modules/", 9 | "/__snapshots__" 10 | ], 11 | "moduleFileExtensions": [ 12 | "ts", 13 | "js" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /packages/reltab-fs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reltab-fs", 3 | "version": "0.14.0", 4 | "description": "filesystem datasource for reltab", 5 | "main": "dist/reltab-fs.js", 6 | "types": "dist/reltab-fs.d.ts", 7 | "scripts": { 8 | "prepublish": "npm run build", 9 | "clean": "rimraf dist", 10 | "build": "tsc -p tsconfig-build.json", 11 | "watch": "tsc -p tsconfig-build.json --watch", 12 | "test": "jest --config jest.config.json --no-cache" 13 | }, 14 | "keywords": [ 15 | "relational", 16 | "sql", 17 | "database", 18 | "sqlite" 19 | ], 20 | "author": "Antony Courtney ", 21 | "license": "MIT", 22 | "dependencies": { 23 | "loglevel": "^1.8.0", 24 | "pretty-hrtime": "^1.0.3" 25 | }, 26 | "devDependencies": { 27 | "@types/jest": "^29.5.14", 28 | "@types/node": "^18.7.18", 29 | "jest": "^29.7.0", 30 | "mkdirp": "^1.0.4", 31 | "prettier": "^2.5.1", 32 | "reltab": "^0.12.0", 33 | "reltab-duckdb": "^0.12.0", 34 | "ts-jest": "^29.2.6", 35 | "typescript": "^4.8.3" 36 | }, 37 | "peerDependencies": { 38 | "reltab": "^0.12.0", 39 | "reltab-duckdb": "^0.12.0" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/reltab-fs/tsconfig-build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["./test/*"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/reltab-fs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist", 4 | "declaration": true, 5 | "sourceMap": true, 6 | "target": "ES5", 7 | "strict": true, 8 | "lib": ["es6", "es2017", "dom"] 9 | }, 10 | "include": ["./src/*", "./test/*.ts"] 11 | } 12 | -------------------------------------------------------------------------------- /packages/reltab-snowflake/jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "transform": { 3 | "^.+\\.tsx?$": "ts-jest" 4 | }, 5 | "testRegex": "/test/.*test.ts", 6 | "testEnvironment": "node", 7 | "testPathIgnorePatterns": ["/node_modules/", "/__snapshots__"], 8 | "moduleFileExtensions": ["ts", "js"], 9 | "testTimeout": 30000 10 | } 11 | -------------------------------------------------------------------------------- /packages/reltab-snowflake/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reltab-snowflake", 3 | "version": "0.14.0", 4 | "description": "Snowflake driver for reltab", 5 | "main": "dist/reltab-snowflake.js", 6 | "types": "dist/reltab-snowflake.d.ts", 7 | "scripts": { 8 | "prepublish": "npm run build", 9 | "clean": "rimraf dist", 10 | "build": "tsc", 11 | "test": "jest --config jest.config.json --no-cache", 12 | "test-resnap": "jest --config jest.config.json --updateSnapshot" 13 | }, 14 | "keywords": [ 15 | "relational", 16 | "sql", 17 | "database", 18 | "snowflake" 19 | ], 20 | "author": "Antony Courtney ", 21 | "license": "MIT", 22 | "dependencies": { 23 | "loglevel": "^1.8.0", 24 | "snowflake-sdk": "^1.6.6" 25 | }, 26 | "devDependencies": { 27 | "@types/jest": "^29.5.14", 28 | "@types/node": "^18.7.18", 29 | "@types/snowflake-sdk": "^1.6.2", 30 | "aggtree": "^0.12.0", 31 | "console.table": "^0.10.0", 32 | "jest": "^29.7.0", 33 | "mkdirp": "^1.0.4", 34 | "prettier": "^2.5.1", 35 | "reltab": "^0.12.0", 36 | "ts-jest": "^29.2.6", 37 | "typescript": "^4.8.3" 38 | }, 39 | "peerDependencies": { 40 | "reltab": "^0.12.0" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/reltab-snowflake/test/basic.test.ts: -------------------------------------------------------------------------------- 1 | import * as reltab from "reltab"; 2 | import { 3 | SnowflakeDriver, 4 | getAuthConnectionOptions, 5 | } from "../src/reltab-snowflake"; 6 | import "../src/reltab-snowflake"; 7 | import * as util from "./testUtils"; 8 | import * as log from "loglevel"; 9 | import * as aggtree from "aggtree"; 10 | import { PathTree } from "aggtree"; 11 | import { DataSourceConnection, DataSourceId } from "reltab"; 12 | import * as snowflake from "snowflake-sdk"; 13 | 14 | let testConn: DataSourceConnection; 15 | 16 | let connOpts = getAuthConnectionOptions(); 17 | connOpts.database = "CITIBIKE"; 18 | connOpts.schema = "PUBLIC"; 19 | 20 | const snowflakeConnKey: DataSourceId = { 21 | providerName: "snowflake", 22 | resourceId: JSON.stringify(connOpts), 23 | }; 24 | 25 | beforeAll(async () => { 26 | // log.setLevel(log.levels.DEBUG); 27 | 28 | testConn = await reltab.getConnection(snowflakeConnKey); 29 | log.debug("got testConn: ", testConn); 30 | const ti = await testConn.getTableSchema("TRIPS"); 31 | log.debug("trips table info: ", ti); 32 | }); 33 | 34 | const tripsTableQuery = reltab.tableQuery("TRIPS"); 35 | 36 | test("t2 - basic table query", async () => { 37 | const qres = await testConn.evalQuery(tripsTableQuery, 0, 20); 38 | 39 | util.logTable(qres, { maxRows: 10 }); 40 | 41 | expect(qres).toMatchSnapshot(); 42 | }); 43 | 44 | test("basic rowcount", async () => { 45 | const rowCount = await testConn.rowCount(tripsTableQuery); 46 | 47 | console.log("row count: ", rowCount); 48 | }); 49 | -------------------------------------------------------------------------------- /packages/reltab-snowflake/test/testUtils.ts: -------------------------------------------------------------------------------- 1 | import * as reltab from "reltab"; 2 | 3 | export const columnSum = ( 4 | tableData: reltab.TableRep, 5 | columnId: string 6 | ): number => { 7 | var sum: number = 0; 8 | 9 | for (var i = 0; i < tableData.rowData.length; i++) { 10 | sum += tableData.rowData[i][columnId] as number; 11 | } 12 | return sum; 13 | }; 14 | 15 | type Handler = (err: any) => void; 16 | 17 | export const mkAsyncErrHandler = (t: any, msg: string): Handler => { 18 | return (err) => { 19 | console.error("caught async promise exception: ", err.stack); 20 | t.fail(msg + ": " + err); 21 | }; 22 | }; 23 | 24 | interface LogTableOptions { 25 | maxRows?: number; 26 | } 27 | 28 | export const logTable = ( 29 | table: reltab.TableRep, 30 | options: LogTableOptions | null = null 31 | ): void => { 32 | // Node's console-table package has slightly different synopsis 33 | // than browser version; accepts column names as first arg: 34 | const ctf: any = console.table; 35 | 36 | const rowData = 37 | options && options.maxRows 38 | ? table.rowData.slice(0, options.maxRows) 39 | : table.rowData; 40 | 41 | ctf(rowData); 42 | }; 43 | -------------------------------------------------------------------------------- /packages/reltab-snowflake/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist", 4 | "declaration": true, 5 | "target": "ES5", 6 | "strict": true, 7 | "lib": ["es6", "es2017", "dom"] 8 | }, 9 | "include": ["./src/*"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/reltab-sqlite/jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "transform": { 3 | "^.+\\.tsx?$": "ts-jest" 4 | }, 5 | "testRegex": "/test/.*test.ts", 6 | "testEnvironment": "node", 7 | "testPathIgnorePatterns": [ 8 | "/node_modules/", 9 | "/__snapshots__" 10 | ], 11 | "moduleFileExtensions": [ 12 | "ts", 13 | "js" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /packages/reltab-sqlite/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reltab-sqlite", 3 | "version": "0.14.0", 4 | "description": "sqlite driver for reltab", 5 | "main": "dist/reltab-sqlite.js", 6 | "types": "dist/reltab-sqlite.d.ts", 7 | "scripts": { 8 | "prepublish": "npm run build", 9 | "clean": "rimraf dist", 10 | "build": "tsc -p tsconfig-build.json", 11 | "test": "jest --config jest.config.json --no-cache" 12 | }, 13 | "keywords": [ 14 | "relational", 15 | "sql", 16 | "database", 17 | "sqlite" 18 | ], 19 | "author": "Antony Courtney ", 20 | "license": "MIT", 21 | "dependencies": { 22 | "@types/sqlite3": "^3.1.8", 23 | "@types/through": "0.0.30", 24 | "byline": "^5.0.0", 25 | "csv-sniffer": "^0.1.1", 26 | "fast-csv": "^4.3.6", 27 | "gauge": "^4.0.0", 28 | "loglevel": "^1.8.0", 29 | "sqlite3": "^5.0.2", 30 | "through": "^2.3.8", 31 | "typed-promisify": "^0.4.0" 32 | }, 33 | "devDependencies": { 34 | "@types/byline": "^4.2.33", 35 | "@types/jest": "^29.5.14", 36 | "@types/node": "^18.7.18", 37 | "jest": "^29.7.0", 38 | "mkdirp": "^1.0.4", 39 | "prettier": "^2.5.1", 40 | "reltab": "^0.12.0", 41 | "ts-jest": "^29.2.6", 42 | "typescript": "^4.8.3" 43 | }, 44 | "peerDependencies": { 45 | "reltab": "^0.12.0" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /packages/reltab-sqlite/test/importNoodle.ts: -------------------------------------------------------------------------------- 1 | import * as sqlite3 from "sqlite3"; 2 | import * as reltab from "reltab"; 3 | import * as reltabSqlite from "../src/reltab-sqlite"; 4 | import * as tp from "typed-promisify"; 5 | import { textSpanContainsPosition } from "typescript"; 6 | import { delimiter } from "path"; 7 | import * as log from "loglevel"; 8 | import * as util from "./testUtils"; 9 | import * as _ from "lodash"; 10 | import { asString } from "reltab"; 11 | 12 | const importCsv = async (db: sqlite3.Database, path: string) => { 13 | const md = await reltabSqlite.fastImport(db, path); 14 | }; 15 | 16 | async function main() { 17 | log.setLevel("info"); // use "debug" for even more verbosity 18 | const ctx = await reltab.getConnection({ 19 | providerName: "sqlite", 20 | resourceId: ":memory:", 21 | }); 22 | 23 | let testCtx = ctx as reltabSqlite.SqliteContext; 24 | 25 | const db = testCtx.db; 26 | 27 | console.log("beforeAll -- starting imports"); 28 | await importCsv(db, "test/support/sample.csv"); 29 | console.log("beforeAll -- imported sample, now importing barttest"); 30 | await importCsv(db, "test/support/barttest.csv"); 31 | 32 | console.log("beforeAll: done with imports"); 33 | } 34 | 35 | main(); 36 | -------------------------------------------------------------------------------- /packages/reltab-sqlite/test/support/barttest.csv: -------------------------------------------------------------------------------- 1 | Name,Title,Base,TCOE,Job Family,Union 2 | "Crunican, Grace",General Manager,312461,399921,Executive Management,Non-Represented 3 | "Dugger, Dorothy",General Manager,298700,419661,Executive Management,Non-Represented 4 | "Burrows, Matthew",General Counsel,238269,321543,Legal & Paralegal,Non-Represented 5 | "Oversier, Paul",Assistant General Manager Operations,212647,288160,Executive Management,Non-Represented 6 | "Schroeder, Scott",Controller-Treasurer,208744,287139,Finance & Accounting,Non-Represented 7 | "DeVaughn, Marcia",Deputy General Manager,206914,281548,Executive Management,Non-Represented 8 | "Crespo, Rodolfo",Chief Transportation Officer,170383,238211,Transportation Operations,Non-Represented 9 | "Lee, Thomas",Senior Attorney,165470,224345,Legal & Paralegal,Non-Represented 10 | "Nuetzel, Victoria",Senior Attorney,160289,220683,Legal & Paralegal,Non-Represented 11 | "Ruffa, Frank","Group Manager, Capital Projects",157300,209709,Engineering & Systems Engineering,Non-Represented 12 | "Dupont, Jean-Luc",Group Manager Systems Capital Prog,152434,212535,Engineering & Systems Engineering,Non-Represented 13 | "Hardy, Leonard",Chief Safety Officer,152125,211941,Safety,Non-Represented 14 | "Hui, Sam",Wayside Inspector,77446,128332,"Maintenance, Vehicle & Facilities",SEIU 15 | "Reinhardt, Michael",Structures Inspector,77446,124962,"Maintenance, Vehicle & Facilities",SEIU 16 | "Estrada, Phillip",Vehicle Inspector,77446,123432,"Maintenance, Vehicle & Facilities",SEIU 17 | "Ornellas, Robert",Revenue Protection Guard,75701,127660,Police,BPOA 18 | "Smith, Daniel",Transit Vehicle Mechanic,74068,104260,"Maintenance, Vehicle & Facilities",SEIU 19 | "Smith, Westley",Train Operator,61184,118799,Transportation Operations,ATU 20 | "Hamill, Kerry",Department Manager Gov't & Comm Rel,130035,181955,Public Affairs & Marketing,Non-Represented 21 | "Serrianni, Cheryl",Station Agent,60790,132030,Transportation Operations,ATU 22 | "Nagel, Kevin",Station Agent,60781,105854,Transportation Operations,ATU 23 | "Riggs, Joshua",Station Agent,60771,115104,Transportation Operations,ATU 24 | "Knight, Joshua",Electrician,59817,113775,"Maintenance, Vehicle & Facilities",SEIU 25 | -------------------------------------------------------------------------------- /packages/reltab-sqlite/test/support/sample.csv: -------------------------------------------------------------------------------- 1 | firstName,lastName,email,phoneNumber 2 | John,Doe,john@doe.com,0123456789 3 | Jane,Doe,jane@doe.com,9876543210 4 | James,Bond,james.bond@mi6.co.uk,0612345678 -------------------------------------------------------------------------------- /packages/reltab-sqlite/test/testUtils.ts: -------------------------------------------------------------------------------- 1 | import * as reltab from "reltab"; 2 | 3 | export const columnSum = ( 4 | tableData: reltab.TableRep, 5 | columnId: string 6 | ): number => { 7 | var sum: number = 0; 8 | 9 | for (var i = 0; i < tableData.rowData.length; i++) { 10 | sum += tableData.rowData[i][columnId] as number; 11 | } 12 | return sum; 13 | }; 14 | 15 | type Handler = (err: any) => void; 16 | 17 | export const mkAsyncErrHandler = (t: any, msg: string): Handler => { 18 | return (err) => { 19 | console.error("caught async promise exception: ", err.stack); 20 | t.fail(msg + ": " + err); 21 | }; 22 | }; 23 | 24 | interface LogTableOptions { 25 | maxRows?: number; 26 | } 27 | 28 | export const logTable = ( 29 | table: reltab.TableRep, 30 | options: LogTableOptions | null = null 31 | ): void => { 32 | // Node's console-table package has slightly different synopsis 33 | // than browser version; accepts column names as first arg: 34 | const ctf: any = console.table; 35 | 36 | const rowData = 37 | options && options.maxRows 38 | ? table.rowData.slice(0, options.maxRows) 39 | : table.rowData; 40 | 41 | ctf(rowData); 42 | }; 43 | -------------------------------------------------------------------------------- /packages/reltab-sqlite/tsconfig-build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["./test/*"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/reltab-sqlite/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist", 4 | "declaration": true, 5 | "target": "ES5", 6 | "strict": true, 7 | "lib": ["es6", "es2017", "dom"] 8 | }, 9 | "include": ["./src/*", "./test/*.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/reltab/jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "transform": { 3 | "^.+\\.tsx?$": "ts-jest" 4 | }, 5 | "testRegex": "/test/.*test.ts", 6 | "testEnvironment": "node", 7 | "testPathIgnorePatterns": [ 8 | "/node_modules/", 9 | "/__snapshots__" 10 | ], 11 | "moduleFileExtensions": [ 12 | "ts", 13 | "js" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /packages/reltab/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reltab", 3 | "version": "0.14.0", 4 | "description": "Backend-independent library for queries on relational tables", 5 | "main": "dist/reltab.js", 6 | "types": "dist/reltab.d.ts", 7 | "scripts": { 8 | "prepublish": "npm run build", 9 | "clean": "rimraf dist", 10 | "build": "tsc -p tsconfig-build.json", 11 | "watch": "tsc -p tsconfig-build.json --watch", 12 | "test": "jest --config jest.config.json" 13 | }, 14 | "keywords": [ 15 | "relational", 16 | "sql", 17 | "database" 18 | ], 19 | "author": "Antony Courtney ", 20 | "license": "MIT", 21 | "dependencies": { 22 | "lodash": "^4.17.21", 23 | "loglevel": "^1.8.0", 24 | "pretty-hrtime": "^1.0.3" 25 | }, 26 | "devDependencies": { 27 | "@types/jest": "^29.5.14", 28 | "@types/lodash": "^4.14.178", 29 | "@types/pretty-hrtime": "^1.0.1", 30 | "jest": "^29.7.0", 31 | "mkdirp": "^1.0.4", 32 | "ts-jest": "^29.2.6", 33 | "typescript": "^4.8.3" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/reltab/src/AggFn.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Aggregation functions. 3 | * 4 | */ 5 | 6 | // We'll eventually want to turn this into a richer type like ColumnType, but let's 7 | // stick with the simple enum for now: 8 | // We'll add "nullstr" here, but don't expect it to show up in any UI; generated 9 | // during toSql elaboration step. 10 | export type AggFn = 11 | | "avg" 12 | | "count" 13 | | "min" 14 | | "max" 15 | | "sum" 16 | | "uniq" 17 | | "null" 18 | | "nullstr"; 19 | 20 | export const basicAggFns: AggFn[] = ["min", "max", "uniq", "null"]; 21 | export const numericAggFns: AggFn[] = [ 22 | "avg", 23 | "count", 24 | "min", 25 | "max", 26 | "sum", 27 | "uniq", 28 | "null", 29 | ]; 30 | -------------------------------------------------------------------------------- /packages/reltab/src/BaseSQLDialect.ts: -------------------------------------------------------------------------------- 1 | import { SQLDialect } from "./dialect"; 2 | import { QueryRep } from "./QueryRep"; 3 | import { SQLQueryAST } from "./SQLQuery"; 4 | import { pagedQueryToSql } from "./toSql"; 5 | import { ColumnType, CoreColumnTypes, ColumnTypeMap } from "./ColumnType"; 6 | import { LeafSchemaMap } from "./TableRep"; 7 | 8 | function escapeQuotes(s: string): string { 9 | return s.replace(/"/g, '""'); 10 | } 11 | 12 | export abstract class BaseSQLDialect implements SQLDialect { 13 | abstract readonly dialectName: string; 14 | readonly requireSubqueryAlias: boolean = false; 15 | readonly allowNonConstExtend: boolean = false; 16 | 17 | quoteCol(cid: string): string { 18 | return '"' + escapeQuotes(cid) + '"'; 19 | } 20 | 21 | ppAggNull(aggStr: string, subExpStr: string, expType: ColumnType): string { 22 | return "null"; 23 | } 24 | 25 | queryToSql( 26 | tableMap: LeafSchemaMap, 27 | query: QueryRep, 28 | offset?: number, 29 | limit?: number 30 | ): SQLQueryAST { 31 | return pagedQueryToSql(this, tableMap, query, offset, limit); 32 | } 33 | 34 | abstract readonly coreColumnTypes: CoreColumnTypes; 35 | abstract columnTypes: ColumnTypeMap; 36 | } 37 | -------------------------------------------------------------------------------- /packages/reltab/src/ColumnStats.ts: -------------------------------------------------------------------------------- 1 | export type NumericSummaryStats = { 2 | statsType: "numeric"; 3 | min: number | null; 4 | max: number | null; 5 | approxUnique: number | null; 6 | count: number; 7 | pctNull: number | null; 8 | }; 9 | 10 | export type TextSummaryStats = { 11 | statsType: "text"; 12 | min: number | null; 13 | max: number | null; 14 | approxUnique: number | null; 15 | count: number; 16 | pctNull: number | null; 17 | }; 18 | 19 | export type ColumnStatsMap = Record< 20 | string, 21 | NumericSummaryStats | TextSummaryStats | null 22 | >; 23 | -------------------------------------------------------------------------------- /packages/reltab/src/ColumnType.ts: -------------------------------------------------------------------------------- 1 | import { AggFn, numericAggFns, basicAggFns } from "./AggFn"; 2 | 3 | // Classification of column types: 4 | export type ColumnKind = 5 | | "string" 6 | | "integer" 7 | | "real" 8 | | "boolean" 9 | | "date" 10 | | "time" 11 | | "datetime" 12 | | "timestamp" 13 | | "blob" 14 | | "dialect"; // unknown; specific to db engine SQL dialect 15 | 16 | export const defaultAggForKind = (kind: ColumnKind): AggFn => { 17 | switch (kind) { 18 | case "string": 19 | case "boolean": 20 | case "date": 21 | case "time": 22 | case "timestamp": 23 | return "uniq"; 24 | case "integer": 25 | case "real": 26 | return "sum"; 27 | default: 28 | return "null"; 29 | } 30 | }; 31 | 32 | const kindIsNumeric = (kind: ColumnKind): boolean => { 33 | switch (kind) { 34 | case "integer": 35 | case "real": 36 | return true; 37 | default: 38 | return false; 39 | } 40 | }; 41 | 42 | export type StringRenderFn = (val: any) => string; 43 | 44 | type ColumnTypeOpts = { 45 | defaultAggFn?: AggFn; 46 | stringRender?: StringRenderFn; 47 | }; 48 | 49 | const defaultValRender: StringRenderFn = (val: any) => { 50 | if (val == null) { 51 | return ""; 52 | } 53 | if (typeof val === "string") { 54 | return val; 55 | } 56 | if (typeof val === "bigint") { 57 | return val.toString(); 58 | } 59 | try { 60 | return String(val); 61 | } catch (err) { 62 | console.error("error stringifying value: ", val); 63 | return ""; 64 | } 65 | }; 66 | 67 | export class ColumnType { 68 | readonly sqlTypeName: string; 69 | readonly kind: ColumnKind; 70 | readonly defaultAggFn: AggFn; 71 | readonly stringRender: StringRenderFn; 72 | 73 | constructor( 74 | sqlTypeName: string, 75 | kind: ColumnKind, 76 | opts: ColumnTypeOpts = {} 77 | ) { 78 | this.sqlTypeName = sqlTypeName; 79 | this.kind = kind; 80 | this.defaultAggFn = 81 | opts.defaultAggFn === undefined 82 | ? defaultAggForKind(kind) 83 | : opts.defaultAggFn; 84 | this.stringRender = 85 | opts.stringRender === undefined ? defaultValRender : opts.stringRender; 86 | } 87 | } 88 | 89 | export interface CoreColumnTypes { 90 | integer: ColumnType; 91 | real: ColumnType; 92 | string: ColumnType; 93 | boolean: ColumnType; 94 | } 95 | 96 | export const colIsNumeric = (ct: ColumnType) => kindIsNumeric(ct.kind); 97 | export const colIsString = (ct: ColumnType) => ct.kind === "string"; 98 | 99 | export const aggFns = (ct: ColumnType): AggFn[] => { 100 | if (colIsNumeric(ct)) { 101 | return numericAggFns; 102 | } 103 | return basicAggFns; 104 | }; 105 | 106 | export type ColumnTypeMap = { [sqlTypeName: string]: ColumnType }; 107 | -------------------------------------------------------------------------------- /packages/reltab/src/QueryRep.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Could almost use an intersection type of {id,type} & ColumnMetadata, but 3 | * properties are all optional here 4 | */ 5 | 6 | import { ColumnType } from "./ColumnType"; 7 | import { AggFn } from "./AggFn"; 8 | import { FilterExp } from "./FilterExp"; 9 | import { ColumnExtendExp } from "./defs"; 10 | 11 | // An AggColSpec is either a column name (for default aggregation based on column type 12 | // or a pair of column name and AggFn 13 | export type AggColSpec = string | [AggFn, string]; 14 | 15 | export type ColumnMapInfo = { 16 | id?: string; 17 | displayName?: string; 18 | }; 19 | 20 | export type ColumnExtendOptions = { 21 | displayName?: string; 22 | type?: ColumnType; 23 | }; 24 | 25 | export interface SqlQueryRep { 26 | operator: "sql"; 27 | sqlQuery: string; 28 | } 29 | 30 | export interface TableQueryRep { 31 | operator: "table"; 32 | tableName: string; 33 | } 34 | export interface ProjectQueryRep { 35 | operator: "project"; 36 | cols: string[]; 37 | from: QueryRep; 38 | } 39 | export interface GroupByQueryRep { 40 | operator: "groupBy"; 41 | cols: string[]; 42 | aggs: AggColSpec[]; 43 | from: QueryRep; 44 | } 45 | export interface FilterQueryRep { 46 | operator: "filter"; 47 | fexp: FilterExp; 48 | from: QueryRep; 49 | } 50 | export interface MapColumnsQueryRep { 51 | operator: "mapColumns"; 52 | cmap: { [colName: string]: ColumnMapInfo }; 53 | from: QueryRep; 54 | } 55 | export interface MapColumnsByIndexQueryRep { 56 | operator: "mapColumnsByIndex"; 57 | cmap: { [colIndex: number]: ColumnMapInfo }; 58 | from: QueryRep; 59 | } 60 | export interface ConcatQueryRep { 61 | operator: "concat"; 62 | target: QueryRep; 63 | from: QueryRep; 64 | } 65 | export interface SortQueryRep { 66 | operator: "sort"; 67 | keys: [string, boolean][]; 68 | from: QueryRep; 69 | } 70 | export interface ExtendQueryRep { 71 | operator: "extend"; 72 | colId: string; 73 | colExp: ColumnExtendExp; 74 | opts: ColumnExtendOptions; 75 | from: QueryRep; 76 | } 77 | // Join types: For now: only left outer 78 | export type JoinType = "LeftOuter"; 79 | export interface JoinQueryRep { 80 | operator: "join"; 81 | rhs: QueryRep; 82 | on: string | string[]; 83 | joinType: JoinType; 84 | lhs: QueryRep; 85 | } 86 | 87 | export type QueryRep = 88 | | SqlQueryRep 89 | | TableQueryRep 90 | | ProjectQueryRep 91 | | GroupByQueryRep 92 | | FilterQueryRep 93 | | MapColumnsQueryRep 94 | | MapColumnsByIndexQueryRep 95 | | ConcatQueryRep 96 | | SortQueryRep 97 | | ExtendQueryRep 98 | | JoinQueryRep; 99 | 100 | // A "leaf dependency" is either a SqlQuery or a table name 101 | export type QueryLeafDep = SqlQueryRep | TableQueryRep; 102 | -------------------------------------------------------------------------------- /packages/reltab/src/TableRep.ts: -------------------------------------------------------------------------------- 1 | import { Schema } from "./Schema"; 2 | import { Scalar } from "./defs"; 3 | 4 | /** 5 | * LeafSchemaMap maps a canonical key for a QueryLeafDep to its Schema 6 | */ 7 | export type LeafSchemaMap = { 8 | [leafKey: string]: Schema; 9 | }; 10 | 11 | export type Row = { 12 | [columnId: string]: Scalar; 13 | }; 14 | 15 | export class TableRep { 16 | schema: Schema; 17 | rowData: Array; 18 | 19 | constructor(schema: Schema, rowData: Array) { 20 | this.schema = schema; 21 | this.rowData = rowData; 22 | } 23 | 24 | getRow(row: number): Row { 25 | return this.rowData[row]; 26 | } 27 | 28 | getColumn(columnId: string): Array { 29 | const idx = this.schema.columnIndex(columnId); 30 | 31 | if (idx === undefined) { 32 | throw new Error('TableRep.getColumn: no such column "' + columnId + '"'); 33 | } 34 | 35 | return this.rowData.map((r) => r[columnId]); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/reltab/src/apiUtils.ts: -------------------------------------------------------------------------------- 1 | import { getConnection } from "./remote/server"; 2 | import { DataSourceNode, DataSourcePath } from "./DataSource"; 3 | import { ReltabConnection } from "./reltab"; 4 | 5 | export async function resolvePath( 6 | rtc: ReltabConnection, 7 | dsPath: DataSourcePath 8 | ): Promise { 9 | const { sourceId, path } = dsPath; 10 | const conn = await rtc.connect(sourceId); 11 | let node: DataSourceNode; 12 | if (path.length < 2) { 13 | node = await conn.getRootNode(); 14 | } else { 15 | // This is awkward and weird, and suggests DataSource.getChildren() should take 16 | // a node, not a path... 17 | const childId = path[path.length - 1]; 18 | const parentDSPath = { sourceId, path: path.slice(0, path.length - 1) }; 19 | let parentChildren = await conn.getChildren(parentDSPath); 20 | const childNode = parentChildren.find((elem) => elem.id === childId); 21 | if (childNode !== undefined) { 22 | node = childNode; 23 | } else { 24 | throw new Error( 25 | "could not resolve data source path " + 26 | JSON.stringify(dsPath) + 27 | ": object not found" 28 | ); 29 | } 30 | } 31 | return node; 32 | } 33 | -------------------------------------------------------------------------------- /packages/reltab/src/d3utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A set of utility functions from [d3-array](https://github.com/d3/d3-array/tree/main), 3 | * 4 | * put here because of ESM / CommonJS module incompatibilities 5 | */ 6 | const e10 = Math.sqrt(50), 7 | e5 = Math.sqrt(10), 8 | e2 = Math.sqrt(2); 9 | 10 | function tickSpec( 11 | start: number, 12 | stop: number, 13 | count: number 14 | ): [number, number, number] { 15 | const step = (stop - start) / Math.max(0, count), 16 | power = Math.floor(Math.log10(step)), 17 | error = step / Math.pow(10, power), 18 | factor = error >= e10 ? 10 : error >= e5 ? 5 : error >= e2 ? 2 : 1; 19 | let i1, i2, inc; 20 | if (power < 0) { 21 | inc = Math.pow(10, -power) / factor; 22 | i1 = Math.round(start * inc); 23 | i2 = Math.round(stop * inc); 24 | if (i1 / inc < start) ++i1; 25 | if (i2 / inc > stop) --i2; 26 | inc = -inc; 27 | } else { 28 | inc = Math.pow(10, power) * factor; 29 | i1 = Math.round(start / inc); 30 | i2 = Math.round(stop / inc); 31 | if (i1 * inc < start) ++i1; 32 | if (i2 * inc > stop) --i2; 33 | } 34 | if (i2 < i1 && 0.5 <= count && count < 2) 35 | return tickSpec(start, stop, count * 2); 36 | return [i1, i2, inc]; 37 | } 38 | 39 | function tickIncrement(start: number, stop: number, count: number): number { 40 | (stop = +stop), (start = +start), (count = +count); 41 | return tickSpec(start, stop, count)[2]; 42 | } 43 | export function nice( 44 | start: number, 45 | stop: number, 46 | count: number 47 | ): [number, number] { 48 | let prestep; 49 | while (true) { 50 | const step = tickIncrement(start, stop, count); 51 | if (step === prestep || step === 0 || !isFinite(step)) { 52 | return [start, stop]; 53 | } else if (step > 0) { 54 | start = Math.floor(start / step) * step; 55 | stop = Math.ceil(stop / step) * step; 56 | } else if (step < 0) { 57 | start = Math.ceil(start * step) / step; 58 | stop = Math.floor(stop * step) / step; 59 | } 60 | prestep = step; 61 | } 62 | } 63 | 64 | // Adapted from https://github.com/d3/d3-array/blob/main/src/threshold/sturges.js 65 | export function thresholdSturges(valuesCount: number) { 66 | return Math.max(1, Math.ceil(Math.log(valuesCount) / Math.LN2) + 1); 67 | } 68 | -------------------------------------------------------------------------------- /packages/reltab/src/dialect.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Definitions and interface for metadata and generation of various SQL dialects 3 | */ 4 | 5 | import { ColumnType, CoreColumnTypes, ColumnTypeMap } from "./ColumnType"; 6 | import * as log from "loglevel"; 7 | import { LeafSchemaMap } from "./TableRep"; 8 | import { QueryRep } from "./QueryRep"; 9 | import { SQLQueryAST } from "./SQLQuery"; 10 | 11 | export interface SQLDialect { 12 | readonly dialectName: string; 13 | readonly requireSubqueryAlias: boolean; 14 | readonly allowNonConstExtend: boolean; 15 | quoteCol(cid: string): string; 16 | ppAggNull(aggStr: string, subExpStr: string, expType: ColumnType): string; 17 | 18 | queryToSql( 19 | tableMap: LeafSchemaMap, 20 | query: QueryRep, 21 | offset?: number, 22 | limit?: number 23 | ): SQLQueryAST; 24 | 25 | readonly coreColumnTypes: CoreColumnTypes; 26 | columnTypes: ColumnTypeMap; 27 | } 28 | 29 | // We'll special case this since it's part of the SQL standard, 30 | // and doesn't fit naturally with the assumption of fixed data type names 31 | const decimalRegex = /^DECIMAL\(\d{1,2},\d{1,2}\)$/; 32 | 33 | export const ensureDialectColumnType = ( 34 | dialect: SQLDialect, 35 | colTypeName: string 36 | ): ColumnType => { 37 | let entry = dialect.columnTypes[colTypeName]; 38 | if (entry == null) { 39 | // special case for DECIMAL(x,y): 40 | if (decimalRegex.test(colTypeName)) { 41 | entry = dialect.columnTypes["DECIMAL"]; 42 | } else { 43 | log.debug( 44 | "no column type found for type name '" + 45 | colTypeName + 46 | "' -- adding entry" 47 | ); 48 | entry = new ColumnType(colTypeName, "dialect"); 49 | } 50 | dialect.columnTypes[colTypeName] = entry; 51 | } 52 | return entry; 53 | }; 54 | -------------------------------------------------------------------------------- /packages/reltab/src/dialectRegistry.ts: -------------------------------------------------------------------------------- 1 | import { SQLDialect } from "./dialect"; 2 | import { SQLiteDialect } from "./dialects/SQLiteDialect"; 3 | import { DuckDBDialect } from "./dialects/DuckDBDialect"; 4 | import { BigQueryDialect } from "./dialects/BigQueryDialect"; 5 | import { PrestoDialect } from "./dialects/PrestoDialect"; 6 | import { SnowflakeDialect } from "./dialects/SnowflakeDialect"; 7 | 8 | export const dialects: { [dialectName: string]: SQLDialect } = { 9 | duckdb: DuckDBDialect, 10 | sqlite: SQLiteDialect, 11 | bigquery: BigQueryDialect, 12 | presto: PrestoDialect, 13 | snowflake: SnowflakeDialect, 14 | }; 15 | 16 | export { BigQueryDialect, PrestoDialect, SQLiteDialect, SnowflakeDialect, DuckDBDialect }; 17 | -------------------------------------------------------------------------------- /packages/reltab/src/dialects/BigQueryDialect.ts: -------------------------------------------------------------------------------- 1 | import { SQLDialect } from "../dialect"; 2 | import { ColumnType, CoreColumnTypes, ColumnTypeMap } from "../ColumnType"; 3 | import * as log from "loglevel"; 4 | import { BaseSQLDialect } from "../BaseSQLDialect"; 5 | 6 | const intCT = new ColumnType("INT64", "integer"); 7 | const floatCT = new ColumnType("FLOAT64", "real"); 8 | const stringCT = new ColumnType("STRING", "string"); 9 | const boolCT = new ColumnType("BOOL", "boolean"); 10 | 11 | const dateCT = new ColumnType("DATE", "date", { 12 | stringRender: (val: any) => (val == null ? "" : val.value), 13 | }); 14 | const timestampCT = new ColumnType("TIMESTAMP", "timestamp", { 15 | stringRender: (val: any) => (val == null ? "" : val.value), 16 | }); 17 | 18 | class BigQueryDialectClass extends BaseSQLDialect { 19 | private static instance: BigQueryDialectClass; 20 | readonly dialectName: string = "bigquery"; 21 | readonly coreColumnTypes: CoreColumnTypes = { 22 | integer: intCT, 23 | real: floatCT, 24 | string: stringCT, 25 | boolean: boolCT, 26 | }; 27 | 28 | readonly columnTypes: ColumnTypeMap = { 29 | INT64: intCT, 30 | FLOAT64: floatCT, 31 | STRING: stringCT, 32 | BOOL: boolCT, 33 | DATE: dateCT, 34 | TIMESTAMP: timestampCT, 35 | }; 36 | 37 | quoteCol(cid: string): string { 38 | return "`" + cid + "`"; 39 | } 40 | 41 | ppAggNull(aggStr: string, subExpStr: string, colType: ColumnType): string { 42 | return `CAST(null as ${colType.sqlTypeName})`; 43 | } 44 | 45 | static getInstance(): BigQueryDialectClass { 46 | if (!BigQueryDialectClass.instance) { 47 | BigQueryDialectClass.instance = new BigQueryDialectClass(); 48 | } 49 | return BigQueryDialectClass.instance; 50 | } 51 | } 52 | 53 | export const BigQueryDialect = BigQueryDialectClass.getInstance(); 54 | -------------------------------------------------------------------------------- /packages/reltab/src/dialects/SQLiteDialect.ts: -------------------------------------------------------------------------------- 1 | import { SQLDialect } from "../dialect"; 2 | import { ColumnType, CoreColumnTypes, ColumnTypeMap } from "../ColumnType"; 3 | import { BaseSQLDialect } from "../BaseSQLDialect"; 4 | 5 | const intCT = new ColumnType("INTEGER", "integer"); 6 | const realCT = new ColumnType("REAL", "real"); 7 | const textCT = new ColumnType("TEXT", "string"); 8 | 9 | export class SQLiteDialectClass extends BaseSQLDialect { 10 | private static instance: SQLiteDialectClass; 11 | readonly dialectName: string = "sqlite"; 12 | readonly coreColumnTypes: CoreColumnTypes = { 13 | integer: intCT, 14 | real: realCT, 15 | string: textCT, 16 | boolean: intCT, 17 | }; 18 | 19 | readonly columnTypes: ColumnTypeMap = { 20 | INTEGER: intCT, 21 | REAL: realCT, 22 | TEXT: textCT, 23 | }; 24 | 25 | static getInstance(): SQLiteDialectClass { 26 | if (!SQLiteDialectClass.instance) { 27 | SQLiteDialectClass.instance = new SQLiteDialectClass(); 28 | } 29 | return SQLiteDialectClass.instance; 30 | } 31 | } 32 | 33 | export const SQLiteDialect = SQLiteDialectClass.getInstance(); 34 | -------------------------------------------------------------------------------- /packages/reltab/src/dialects/SnowflakeDialect.ts: -------------------------------------------------------------------------------- 1 | import { SQLDialect } from "../dialect"; 2 | import { ColumnType, CoreColumnTypes, ColumnTypeMap } from "../ColumnType"; 3 | import * as log from "loglevel"; 4 | import { BaseSQLDialect } from "../BaseSQLDialect"; 5 | 6 | const numberCT = new ColumnType("NUMBER", "integer"); 7 | const floatCT = new ColumnType("FLOAT", "real"); 8 | const stringCT = new ColumnType("VARCHAR", "string"); 9 | const boolCT = new ColumnType("BOOLEAN", "boolean"); 10 | 11 | const dateCT = new ColumnType("DATE", "date"); 12 | const timestampCT = new ColumnType("TIMESTAMP_NTZ", "timestamp"); 13 | 14 | class SnowflakeDialectClass extends BaseSQLDialect { 15 | private static instance: SnowflakeDialectClass; 16 | readonly dialectName: string = "snowflake"; 17 | readonly coreColumnTypes: CoreColumnTypes = { 18 | integer: numberCT, 19 | real: floatCT, 20 | string: stringCT, 21 | boolean: boolCT, 22 | }; 23 | 24 | readonly columnTypes: ColumnTypeMap = { 25 | NUMBER: numberCT, 26 | FLOAT: floatCT, 27 | STRING: stringCT, 28 | VARCHAR: stringCT, 29 | BOOL: boolCT, 30 | DATE: dateCT, 31 | TIMESTAMP: timestampCT, 32 | }; 33 | 34 | quoteCol(cid: string): string { 35 | return '"' + cid + '"'; 36 | } 37 | 38 | ppAggNull(aggStr: string, subExpStr: string, colType: ColumnType): string { 39 | return `CAST(null as ${colType.sqlTypeName})`; 40 | } 41 | 42 | static getInstance(): SnowflakeDialectClass { 43 | if (!SnowflakeDialectClass.instance) { 44 | SnowflakeDialectClass.instance = new SnowflakeDialectClass(); 45 | } 46 | return SnowflakeDialectClass.instance; 47 | } 48 | } 49 | 50 | export const SnowflakeDialect = SnowflakeDialectClass.getInstance(); 51 | -------------------------------------------------------------------------------- /packages/reltab/src/reltab.ts: -------------------------------------------------------------------------------- 1 | export * from "./dialectRegistry"; 2 | export * from "./dialect"; 3 | export * from "./remote/Connection"; 4 | export { getConnection } from "./remote/server"; 5 | export * from "./defs"; 6 | export * from "./histogram"; 7 | export * from "./FilterExp"; 8 | export * from "./QueryExp"; 9 | export * from "./ColumnStats"; 10 | export * from "./Schema"; 11 | export * from "./TableRep"; 12 | export * from "./AggFn"; 13 | export * from "./ColumnType"; 14 | export * from "./DataSource"; 15 | export * from "./toSql"; 16 | export { AggColSpec, ColumnMapInfo } from "./QueryRep"; 17 | export * from "./remote/Transport"; 18 | export * from "./remote/server"; 19 | export * from "./apiUtils"; 20 | -------------------------------------------------------------------------------- /packages/reltab/src/remote/Transport.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Generic interface for simple, reliable remote function and method invocation. 3 | */ 4 | 5 | export interface TransportClient { 6 | invoke(functionName: string, encodedReq: string): Promise; 7 | } 8 | 9 | export type EncodedRequestHandler = (encodedReq: string) => Promise; 10 | 11 | export interface TransportServer { 12 | registerInvokeHandler( 13 | functionName: string, 14 | handler: EncodedRequestHandler 15 | ): void; 16 | } 17 | -------------------------------------------------------------------------------- /packages/reltab/src/remote/errorUtils.ts: -------------------------------------------------------------------------------- 1 | export function serializeError(err: Error): Error { 2 | const { name, message, stack } = err; 3 | const rep = { name, message, stack }; 4 | return rep; 5 | } 6 | 7 | export function deserializeError(errObj: Error): Error { 8 | const { name, message, stack } = errObj; 9 | const ret = new Error(message); 10 | ret.name = name; 11 | ret.stack = stack; 12 | return ret; 13 | } 14 | -------------------------------------------------------------------------------- /packages/reltab/src/remote/result.ts: -------------------------------------------------------------------------------- 1 | export interface OkResult { 2 | status: "Ok"; 3 | value: T; 4 | } 5 | 6 | export interface ErrResult { 7 | status: "Err"; 8 | errVal: Error; 9 | } 10 | 11 | export type Result = OkResult | ErrResult; 12 | -------------------------------------------------------------------------------- /packages/reltab/src/util/environ.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * A snapshot of the "environ" npm module from: 4 | * https://github.com/kjs3/environ/blob/master/src/index.ts 5 | * 6 | * Copied here because there are issues with the source map in the packaged 7 | * npm module; replace with the original module if/when this is fixed. 8 | */ 9 | declare const Deno: any; 10 | declare const process: any; 11 | declare const navigator: any; 12 | 13 | export function isBrowser(): boolean { 14 | return hasWindow() && hasDocument(); 15 | } 16 | 17 | // TODO: figure out how to test this 18 | export function isWebWorker(): boolean { 19 | return false; // 🤷‍♂️ 20 | } 21 | 22 | export function isNode(): boolean { 23 | return hasGlobal(); 24 | } 25 | 26 | export function isElectron(): boolean { 27 | // https://github.com/electron/electron/issues/2288#issuecomment-611231970 28 | return isElectronMain() || isElectronRenderer(); 29 | } 30 | 31 | // TODO: figure out how to test this 32 | export function isElectronMain(): boolean { 33 | try { 34 | return Object.keys(process.versions).some((key) => key === "electron"); 35 | } catch (e) { 36 | return false; 37 | } 38 | } 39 | 40 | export function isElectronRenderer(): boolean { 41 | try { 42 | return /electron/i.test(navigator.userAgent); 43 | } catch (e) { 44 | return false; 45 | } 46 | } 47 | 48 | export function isDeno(): boolean { 49 | try { 50 | return typeof Deno === "object" && !!Deno.pid; 51 | } catch (e) { 52 | return false; 53 | } 54 | } 55 | 56 | export function isJsDom(): boolean { 57 | // https://github.com/jsdom/jsdom/issues/1537#issuecomment-229405327 58 | try { 59 | return hasWindow() && navigator.userAgent.includes("jsdom"); 60 | } catch (e) { 61 | return false; 62 | } 63 | } 64 | 65 | function hasGlobal() { 66 | return new Function("try {return this===global}catch(e){ return false}")(); 67 | } 68 | 69 | function hasWindow() { 70 | return new Function("try {return this===window}catch(e){ return false}")(); 71 | } 72 | 73 | function hasDocument() { 74 | return new Function( 75 | "try {return this.document !== undefined}catch(e){ return false}" 76 | )(); 77 | } 78 | -------------------------------------------------------------------------------- /packages/reltab/test/basic.test.ts: -------------------------------------------------------------------------------- 1 | import * as reltab from "../src/reltab"; 2 | 3 | test("t0 - basic table query rep", () => { 4 | const q0 = reltab.tableQuery("barttest"); 5 | 6 | console.log("q0: ", JSON.stringify(q0, undefined, 2)); 7 | }); 8 | -------------------------------------------------------------------------------- /packages/reltab/test/serErr.test.ts: -------------------------------------------------------------------------------- 1 | import { serializeError, deserializeError } from "../src/remote/errorUtils"; 2 | 3 | test("basic error serialization", () => { 4 | const err = new Error("Badness"); 5 | 6 | console.log("err: ", err); 7 | const s = JSON.stringify({ err }); 8 | console.log("JSON-ified error: ", s); 9 | 10 | const s2 = JSON.stringify({ err: serializeError(err) }); 11 | console.log("serialized error: ", s2); 12 | 13 | const jsonObj = JSON.parse(s2); 14 | const e2 = deserializeError(jsonObj.err); 15 | 16 | console.log("de-serialized Error: ", e2); 17 | }); 18 | -------------------------------------------------------------------------------- /packages/reltab/tsconfig-build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["./test/*"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/reltab/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist", 4 | "declaration": true, 5 | "module": "CommonJS", 6 | "sourceMap": true, 7 | "target": "ES2015", 8 | "strict": true, 9 | "lib": ["es6", "es2017", "dom"] 10 | }, 11 | "include": ["./src/*.ts*", "./test/*.ts*"] 12 | } 13 | -------------------------------------------------------------------------------- /packages/tad-app/app/preload.js: -------------------------------------------------------------------------------- 1 | /* preload script for renderer process */ 2 | // const { contextBridge } = require("electron"); 3 | /* 4 | contextBridge.exposeInMainWorld( 5 | "openParams", 6 | JSON.parse(process.argv[process.argv.length - 1]) 7 | ); 8 | */ 9 | // Note: The openParams were appended to process.argv via 10 | // the webPreferences.additionalArguments passed to BrowserWindow 11 | // constructor. 12 | const argPrefix = '--tadOpenParams='; 13 | for (let i = 0; i < process.argv.length; i++) { 14 | const arg = process.argv[i]; 15 | if (arg.startsWith(argPrefix)) { 16 | const openParamsStr = arg.slice(argPrefix.length); 17 | window.openParams = JSON.parse(openParamsStr); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/tad-app/app/quickStart.ts: -------------------------------------------------------------------------------- 1 | import url from "url"; 2 | 3 | import path from "path"; 4 | 5 | import log from "electron-log"; 6 | 7 | import electron, { BrowserWindow, ipcMain } from "electron"; 8 | import * as appWindow from "./appWindow"; 9 | 10 | const app = electron.app; 11 | let win: BrowserWindow | null = null; 12 | 13 | function getSetupInfo(): any { 14 | const platform = process.platform; 15 | const appPath = app.getAppPath(); 16 | const appDir = path.dirname(appPath); 17 | const appScriptPath = path.join(appDir, "tad.sh"); 18 | const exePath = app.getPath("exe"); 19 | const exeDir = path.dirname(exePath); 20 | const setupInfo = { 21 | platform, 22 | appDir, 23 | appScriptPath, 24 | exePath, 25 | exeDir, 26 | }; 27 | return setupInfo; 28 | } 29 | 30 | function openExample() { 31 | const app = electron.app; 32 | const appPath = app.getAppPath(); 33 | const appDir = process.defaultApp ? appPath : path.dirname(appPath); 34 | const exampleFilePath = path.join(appDir, "examples", "movie_metadata.csv"); 35 | appWindow.createFromFile(exampleFilePath); 36 | } 37 | 38 | export const showQuickStart = () => { 39 | if (!win) { 40 | ipcMain.handle("getSetupInfo", getSetupInfo); 41 | ipcMain.handle("openExample", openExample); 42 | win = new BrowserWindow({ 43 | width: 850, 44 | height: 600, 45 | webPreferences: { 46 | nodeIntegration: true, 47 | contextIsolation: false, 48 | }, 49 | }); 50 | win.loadURL( 51 | url.format({ 52 | pathname: path.join(__dirname, "userdocs", "quickstart.html"), 53 | protocol: "file:", 54 | slashes: true, 55 | }) 56 | ); 57 | win.on("close", (e) => { 58 | win = null; 59 | }); 60 | } 61 | 62 | win.show(); 63 | }; 64 | -------------------------------------------------------------------------------- /packages/tad-app/app/setup.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * electron-builer doesn't seem to provide a bespoke post-install step, so 4 | * we'll do this at application startup time by checking for a version-specific 5 | * marker file in the filesystem. 6 | * 7 | */ 8 | import path from "path"; 9 | import fs from "fs"; 10 | import log from "electron-log"; 11 | import electron from "electron"; 12 | import { URL } from "url"; 13 | const app = electron.app; 14 | const isDarwin = process.platform === "darwin"; 15 | const MARKER_BASENAME = "install-marker-"; 16 | 17 | /** 18 | * @returns true iff we're running the packaged version of the app 19 | */ 20 | export const runningPackaged = (): boolean => { 21 | const appPath = app.getAppPath(); 22 | const appBase = path.basename(appPath); 23 | 24 | return appBase === "app.asar"; 25 | }; 26 | 27 | const postInstall = (markerPath: string | number | Buffer | URL) => { 28 | const appPath = app.getAppPath(); 29 | const appBase = path.basename(appPath); 30 | 31 | if (appBase !== "app.asar") { 32 | // We can only create the symbolic link to the packaged app 33 | log.info( 34 | "postInsall: not running from packaged app, skipping post-install" 35 | ); 36 | return; 37 | } // and create the marker: 38 | 39 | fs.writeFileSync(markerPath, ""); 40 | }; 41 | /* 42 | * check for marker file and run post install step if it doesn't exist 43 | * 44 | * returns: true iff first install detected 45 | */ 46 | 47 | export const postInstallCheck = () => { 48 | let firstInstall = false; 49 | const userDataPath = app.getPath("userData"); 50 | const versionStr = app.getVersion().replace(/\./g, "_"); 51 | const markerFilename = MARKER_BASENAME + versionStr + ".txt"; 52 | const markerPath = path.join(userDataPath, markerFilename); 53 | log.debug("postInstallCheck: looking for install marker file ", markerPath); 54 | 55 | if (fs.existsSync(markerPath)) { 56 | log.debug( 57 | "postInstalCheck: found marker file, skipping post-install setup" 58 | ); 59 | } else { 60 | log.warn( 61 | "postInstallCheck: install marker file not found, performing post-install step." 62 | ); 63 | firstInstall = true; 64 | 65 | try { 66 | postInstall(markerPath); 67 | log.warn("postInstallCheck: postInstall complete"); 68 | } catch (e) { 69 | log.error("postInstallCheck: ", (e as any).message); 70 | log.error((e as any).stack); 71 | } 72 | } 73 | 74 | return firstInstall; 75 | }; 76 | -------------------------------------------------------------------------------- /packages/tad-app/app/updater.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * updater.js 3 | * 4 | * Based on code snippet from electron-builder docs found here: 5 | * https://github.com/electron-userland/electron-builder/blob/master/docs/encapsulated%20manual%20update%20via%20menu.js 6 | */ 7 | import { dialog, BrowserWindow, MenuItem } from "electron"; 8 | import { autoUpdater } from "electron-updater"; 9 | 10 | let updater: MenuItem | null; 11 | autoUpdater.autoDownload = false; 12 | autoUpdater.on("error", (event, error) => { 13 | dialog.showErrorBox( 14 | "Error: ", 15 | error == null ? "unknown" : (error.stack || error).toString() 16 | ); 17 | }); 18 | autoUpdater.on("update-available", () => { 19 | const buttonIndex = dialog.showMessageBoxSync({ 20 | type: "info", 21 | title: "Found Updates", 22 | message: "A new release of Tad is available. Do you want to update now?", 23 | buttons: ["Yes", "No"], 24 | }); 25 | if (buttonIndex === 0) { 26 | autoUpdater.downloadUpdate(); 27 | } else { 28 | updater!.enabled = true; 29 | updater = null; 30 | } 31 | }); 32 | autoUpdater.on("update-not-available", () => { 33 | dialog.showMessageBox({ 34 | title: "No Updates", 35 | message: "Current version is up-to-date.", 36 | }); 37 | updater!.enabled = true; 38 | updater = null; 39 | }); 40 | autoUpdater.on("update-downloaded", () => { 41 | dialog.showMessageBoxSync({ 42 | title: "Install Updates", 43 | message: "Update downloaded, will update...", 44 | }); 45 | autoUpdater.quitAndInstall(); 46 | }); 47 | 48 | // export this to MenuItem click callback 49 | export function checkForUpdates( 50 | menuItem: MenuItem, 51 | focusedWindow: BrowserWindow 52 | ) { 53 | console.log("updater.checkForUpdates"); 54 | updater = menuItem; 55 | updater.enabled = false; 56 | autoUpdater.checkForUpdates(); 57 | } 58 | -------------------------------------------------------------------------------- /packages/tad-app/buildRes/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonycourtney/tad/272ffa0092024cd57b82fc813af1f8c6568bc3f0/packages/tad-app/buildRes/icon.icns -------------------------------------------------------------------------------- /packages/tad-app/csv/12345.csv: -------------------------------------------------------------------------------- 1 | Name,Title,Base,TCOE,Job Family,Union 2 | "Crunican, Grace",General Manager,312461,399921,Executive Management,Non-Represented 3 | "Dugger, Dorothy",General Manager,298700,419661,Executive Management,Non-Represented 4 | "Burrows, Matthew",General Counsel,238269,321543,Legal & Paralegal,Non-Represented 5 | "Oversier, Paul",Assistant General Manager Operations,212647,288160,Executive Management,Non-Represented 6 | "Schroeder, Scott",Controller-Treasurer,208744,287139,Finance & Accounting,Non-Represented 7 | "DeVaughn, Marcia",Deputy General Manager,206914,281548,Executive Management,Non-Represented 8 | "Crespo, Rodolfo",Chief Transportation Officer,170383,238211,Transportation Operations,Non-Represented 9 | "Lee, Thomas",Senior Attorney,165470,224345,Legal & Paralegal,Non-Represented 10 | "Nuetzel, Victoria",Senior Attorney,160289,220683,Legal & Paralegal,Non-Represented 11 | "Ruffa, Frank","Group Manager, Capital Projects",157300,209709,Engineering & Systems Engineering,Non-Represented 12 | "Dupont, Jean-Luc",Group Manager Systems Capital Prog,152434,212535,Engineering & Systems Engineering,Non-Represented 13 | "Hardy, Leonard",Chief Safety Officer,152125,211941,Safety,Non-Represented 14 | "Hui, Sam",Wayside Inspector,77446,128332,"Maintenance, Vehicle & Facilities",SEIU 15 | "Reinhardt, Michael",Structures Inspector,77446,124962,"Maintenance, Vehicle & Facilities",SEIU 16 | "Estrada, Phillip",Vehicle Inspector,77446,123432,"Maintenance, Vehicle & Facilities",SEIU 17 | "Ornellas, Robert",Revenue Protection Guard,75701,127660,Police,BPOA 18 | "Smith, Daniel",Transit Vehicle Mechanic,74068,104260,"Maintenance, Vehicle & Facilities",SEIU 19 | "Smith, Westley",Train Operator,61184,118799,Transportation Operations,ATU 20 | "Hamill, Kerry",Department Manager Gov't & Comm Rel,130035,181955,Public Affairs & Marketing,Non-Represented 21 | "Serrianni, Cheryl",Station Agent,60790,132030,Transportation Operations,ATU 22 | "Nagel, Kevin",Station Agent,60781,105854,Transportation Operations,ATU 23 | "Riggs, Joshua",Station Agent,60771,115104,Transportation Operations,ATU 24 | "Knight, Joshua",Electrician,59817,113775,"Maintenance, Vehicle & Facilities",SEIU 25 | -------------------------------------------------------------------------------- /packages/tad-app/csv/backslashtest.csv: -------------------------------------------------------------------------------- 1 | Name,Title,Base,TCOE,Job Family,Union 2 | "Crunican, Grace",General Manager,312461,399921,Executive Management,Non-Represented 3 | "Dugger, Dorothy",General Manager,298700,419661,Executive Management,Non-Represented 4 | "Burrows, Matthew",General Counsel,238269,321543,\Legal & Paralegal,Non-Represented 5 | "Oversier, Paul",Assistant General Manager Operations,212647,288160,Executive Management,Non-Represented 6 | "Schroeder, Scott",Controller-Treasurer,208744,287139,Finance & Accounting,Non-Represented 7 | "DeVaughn, Marcia",Deputy General Manager,206914,281548,Executive Management,Non-Represented 8 | "Crespo, Rodolfo",Chief Transportation Officer,170383,238211,Transportation Operations,Non-Represented 9 | "Lee, Thomas",Senior Attorney,165470,224345,\Legal & Paralegal,Non-Represented 10 | "Nuetzel, Victoria",Senior Attorney,160289,220683,\Legal & Paralegal,Non-Represented 11 | "Ruffa, Frank","Group Manager, Capital Projects",157300,209709,Engineering & Systems Engineering,Non-Represented 12 | "Dupont, Jean-Luc",Group Manager Systems Capital Prog,152434,212535,Engineering & Systems Engineering,Non-Represented 13 | "Hardy, Leonard",Chief Safety Officer,152125,211941,Safety,Non-Represented 14 | "Hui, Sam",Wayside Inspector,77446,128332,"Maintenance, Vehicle & Facilities",SEIU 15 | "Reinhardt, Michael",Structures Inspector,77446,124962,"Maintenance, Vehicle & Facilities",SEIU 16 | "Estrada, Phillip",Vehicle Inspector,77446,123432,"Maintenance, Vehicle & Facilities",SEIU 17 | "Ornellas, Robert",Revenue Protection Guard,75701,127660,Police,BPOA 18 | "Smith, Daniel",Transit Vehicle Mechanic,74068,104260,"Maintenance, Vehicle & Facilities",SEIU 19 | "Smith, Westley",Train Operator,61184,118799,Transportation Operations,ATU 20 | "Hamill, Kerry",Department Manager Gov't & Comm Rel,130035,181955,Public Affairs & Marketing,Non-Represented 21 | "Serrianni, Cheryl",Station Agent,60790,132030,Transportation Operations,ATU 22 | "Nagel, Kevin",Station Agent,60781,105854,Transportation Operations,ATU 23 | "Riggs, Joshua",Station Agent,60771,115104,Transportation Operations,ATU 24 | "Knight, Joshua",Electrician,59817,113775,"Maintenance, Vehicle & Facilities",SEIU 25 | -------------------------------------------------------------------------------- /packages/tad-app/csv/badfmt.csv: -------------------------------------------------------------------------------- 1 | Name,Title,Base,TCOE,Job Family,Union "Crunican, Grace",General Manager,312461,399921,Executive Management,Non-Represented "Dugger, Dorothy",General Manager,298700,419661,Executive Management,Non-Represented "Burrows, Matthew",General Counsel,238269,321543,Legal & Paralegal,Non-Represented "Oversier, Paul",Assistant General Manager Operations,212647,288160,Executive Management,Non-Represented "Schroeder, Scott",Controller-Treasurer,208744,287139,Finance & Accounting,Non-Represented "DeVaughn, Marcia",Deputy General Manager,206914,281548,Executive Management,Non-Represented "Crespo, Rodolfo",Chief Transportation Officer,170383,238211,Transportation Operations,Non-Represented "Lee, Thomas",Senior Attorney,165470,224345,Legal & Paralegal,Non-Represented "Nuetzel, Victoria",Senior Attorney,160289,220683,Legal & Paralegal,Non-Represented "Ruffa, Frank","Group Manager, Capital Projects",157300,209709,Engineering & Systems Engineering,Non-Represented "Dupont, Jean-Luc",Group Manager Systems Capital Prog,152434,212535,Engineering & Systems Engineering,Non-Represented "Hardy, Leonard",Chief Safety Officer,152125,211941,Safety,Non-Represented "Hui, Sam",Wayside Inspector,77446,128332,"Maintenance, Vehicle & Facilities",SEIU "Reinhardt, Michael",Structures Inspector,77446,124962,"Maintenance, Vehicle & Facilities",SEIU "Estrada, Phillip",Vehicle Inspector,77446,123432,"Maintenance, Vehicle & Facilities",SEIU "Ornellas, Robert",Revenue Protection Guard,75701,127660,Police,BPOA "Smith, Daniel",Transit Vehicle Mechanic,74068,104260,"Maintenance, Vehicle & Facilities",SEIU "Smith, Westley",Train Operator,61184,118799,Transportation Operations,ATU "Hamill, Kerry",Department Manager Gov't & Comm Rel,130035,181955,Public Affairs & Marketing,Non-Represented "Serrianni, Cheryl",Station Agent,60790,132030,Transportation Operations,ATU "Nagel, Kevin",Station Agent,60781,105854,Transportation Operations,ATU "Riggs, Joshua",Station Agent,60771,115104,Transportation Operations,ATU "Knight, Joshua",Electrician,59817,113775,"Maintenance, Vehicle & Facilities",SEIU -------------------------------------------------------------------------------- /packages/tad-app/csv/barttest-no-headers.csv: -------------------------------------------------------------------------------- 1 | "Crunican, Grace",General Manager,312461,399921,Executive Management,Non-Represented 2 | "Dugger, Dorothy",General Manager,298700,419661,Executive Management,Non-Represented 3 | "Burrows, Matthew",General Counsel,238269,321543,Legal & Paralegal,Non-Represented 4 | "Oversier, Paul",Assistant General Manager Operations,212647,288160,Executive Management,Non-Represented 5 | "Schroeder, Scott",Controller-Treasurer,208744,287139,Finance & Accounting,Non-Represented 6 | "DeVaughn, Marcia",Deputy General Manager,206914,281548,Executive Management,Non-Represented 7 | "Crespo, Rodolfo",Chief Transportation Officer,170383,238211,Transportation Operations,Non-Represented 8 | "Lee, Thomas",Senior Attorney,165470,224345,Legal & Paralegal,Non-Represented 9 | "Nuetzel, Victoria",Senior Attorney,160289,220683,Legal & Paralegal,Non-Represented 10 | "Ruffa, Frank","Group Manager, Capital Projects",157300,209709,Engineering & Systems Engineering,Non-Represented 11 | "Dupont, Jean-Luc",Group Manager Systems Capital Prog,152434,212535,Engineering & Systems Engineering,Non-Represented 12 | "Hardy, Leonard",Chief Safety Officer,152125,211941,Safety,Non-Represented 13 | "Hui, Sam",Wayside Inspector,77446,128332,"Maintenance, Vehicle & Facilities",SEIU 14 | "Reinhardt, Michael",Structures Inspector,77446,124962,"Maintenance, Vehicle & Facilities",SEIU 15 | "Estrada, Phillip",Vehicle Inspector,77446,123432,"Maintenance, Vehicle & Facilities",SEIU 16 | "Ornellas, Robert",Revenue Protection Guard,75701,127660,Police,BPOA 17 | "Smith, Daniel",Transit Vehicle Mechanic,74068,104260,"Maintenance, Vehicle & Facilities",SEIU 18 | "Smith, Westley",Train Operator,61184,118799,Transportation Operations,ATU 19 | "Hamill, Kerry",Department Manager Gov't & Comm Rel,130035,181955,Public Affairs & Marketing,Non-Represented 20 | "Serrianni, Cheryl",Station Agent,60790,132030,Transportation Operations,ATU 21 | "Nagel, Kevin",Station Agent,60781,105854,Transportation Operations,ATU 22 | "Riggs, Joshua",Station Agent,60771,115104,Transportation Operations,ATU 23 | "Knight, Joshua",Electrician,59817,113775,"Maintenance, Vehicle & Facilities",SEIU 24 | -------------------------------------------------------------------------------- /packages/tad-app/csv/barttest.csv: -------------------------------------------------------------------------------- 1 | Name,Title,Base,TCOE,Job Family,Union 2 | "Crunican, Grace",General Manager,312461,399921,Executive Management,Non-Represented 3 | "Dugger, Dorothy",General Manager,298700,419661,Executive Management,Non-Represented 4 | "Burrows, Matthew",General Counsel,238269,321543,Legal & Paralegal,Non-Represented 5 | "Oversier, Paul",Assistant General Manager Operations,212647,288160,Executive Management,Non-Represented 6 | "Schroeder, Scott",Controller-Treasurer,208744,287139,Finance & Accounting,Non-Represented 7 | "DeVaughn, Marcia",Deputy General Manager,206914,281548,Executive Management,Non-Represented 8 | "Crespo, Rodolfo",Chief Transportation Officer,170383,238211,Transportation Operations,Non-Represented 9 | "Lee, Thomas",Senior Attorney,165470,224345,Legal & Paralegal,Non-Represented 10 | "Nuetzel, Victoria",Senior Attorney,160289,220683,Legal & Paralegal,Non-Represented 11 | "Ruffa, Frank","Group Manager, Capital Projects",157300,209709,Engineering & Systems Engineering,Non-Represented 12 | "Dupont, Jean-Luc",Group Manager Systems Capital Prog,152434,212535,Engineering & Systems Engineering,Non-Represented 13 | "Hardy, Leonard",Chief Safety Officer,152125,211941,Safety,Non-Represented 14 | "Hui, Sam",Wayside Inspector,77446,128332,"Maintenance, Vehicle & Facilities",SEIU 15 | "Reinhardt, Michael",Structures Inspector,77446,124962,"Maintenance, Vehicle & Facilities",SEIU 16 | "Estrada, Phillip",Vehicle Inspector,77446,123432,"Maintenance, Vehicle & Facilities",SEIU 17 | "Ornellas, Robert",Revenue Protection Guard,75701,127660,Police,BPOA 18 | "Smith, Daniel",Transit Vehicle Mechanic,74068,104260,"Maintenance, Vehicle & Facilities",SEIU 19 | "Smith, Westley",Train Operator,61184,118799,Transportation Operations,ATU 20 | "Hamill, Kerry",Department Manager Gov't & Comm Rel,130035,181955,Public Affairs & Marketing,Non-Represented 21 | "Serrianni, Cheryl",Station Agent,60790,132030,Transportation Operations,ATU 22 | "Nagel, Kevin",Station Agent,60781,105854,Transportation Operations,ATU 23 | "Riggs, Joshua",Station Agent,60771,115104,Transportation Operations,ATU 24 | "Knight, Joshua",Electrician,59817,113775,"Maintenance, Vehicle & Facilities",SEIU 25 | -------------------------------------------------------------------------------- /packages/tad-app/csv/ecsv-example-basic.csv: -------------------------------------------------------------------------------- 1 | "";"id";"float";"int";"name";"date";"logical" 2 | "1";"10000";0,189614180182718;1.234.567;"a b";2015-04-05;FALSE 3 | "2";"10001";-0,445634937641628;5.678.912;"c d";2015-03-12;FALSE 4 | "3";"10001";-0,557006757297433;3.456;"e f";2015-04-05;TRUE 5 | -------------------------------------------------------------------------------- /packages/tad-app/csv/ecsv-example-noheaders.csv: -------------------------------------------------------------------------------- 1 | "1";"10000";0,189614180182718;1.234.567;"a b";2015-04-05;FALSE 2 | "2";"10001";-0,445634937641628;5.678.912;"c d";2015-03-12;FALSE 3 | "3";"10001";-0,557006757297433;3.456;"e f";2015-04-05;TRUE 4 | -------------------------------------------------------------------------------- /packages/tad-app/csv/htmldata.csv: -------------------------------------------------------------------------------- 1 | Name,Title,Base,TCOE,Job Family,Union 2 | "Crunican, Grace",General Manager,312461,399921,Executive Management,Non-Represented 3 | "Dugger, Dorothy",General Manager,298700,419661,Executive Management,Non-Represented 4 | "Burrows, Matthew",General Counsel,238269,321543,Legal & Paralegal,Non-Represented 5 | "Oversier, Paul",Assistant General Manager Operations,212647,288160,Executive Management,Non-Represented 6 | "Schroeder, Scott",Controller-Treasurer,208744,287139,Finance & Accounting,Non-Represented 7 | "DeVaughn, Marcia",Deputy General Manager,206914,281548,Executive Management,Non-Represented 8 | "Crespo, Rodolfo",Chief Transportation Officer,170383,238211,Transportation Operations,Non-Represented 9 | "Lee, Thomas",Senior Attorney,165470,224345,Legal & Paralegal,Non-Represented 10 | "Nuetzel, Victoria",Senior Attorney,160289,220683,Legal & Paralegal,Non-Represented 11 | "Ruffa, Frank","Group Manager, Capital Projects",157300,209709,Engineering & Systems Engineering,Non-Represented 12 | "Dupont, Jean-Luc",Group Manager Systems Capital Prog,152434,212535,Engineering & Systems Engineering,Non-Represented 13 | "Hardy, Leonard",Chief Safety Officer,152125,211941,Safety,Non-Represented 14 | "Hui, Sam",Wayside Inspector,77446,128332,"Maintenance, Vehicle & Facilities",SEIU 15 | "Reinhardt, Michael",Structures Inspector,77446,124962,"Maintenance, Vehicle & Facilities",SEIU 16 | "Estrada, Phillip",Vehicle Inspector,77446,123432,"Maintenance, Vehicle & Facilities",SEIU 17 | "Ornellas, Robert",Revenue Protection Guard,75701,127660,Police,BPOA 18 | "Smith, Daniel",Transit Vehicle Mechanic,74068,104260,"Maintenance, Vehicle & Facilities",SEIU 19 | "Smith, Westley",Train Operator,61184,118799,Transportation Operations,ATU 20 | "Hamill, Kerry",Department Manager Gov't & Comm Rel,130035,181955,Public Affairs & Marketing,Non-Represented 21 | "Serrianni, Cheryl",Station Agent,60790,132030,Transportation Operations,ATU 22 | "Nagel, Kevin",Station Agent,60781,105854,Transportation Operations,ATU 23 | "Riggs, Joshua",Station Agent,60771,115104,Transportation Operations,ATU 24 | "Knight, Joshua",Electrician,59817,113775,"Maintenance, Vehicle & Facilities",SEIU 25 | -------------------------------------------------------------------------------- /packages/tad-app/csv/movie-grossByCountryDirector.tad: -------------------------------------------------------------------------------- 1 | { 2 | "tadFileFormatVersion": 1, 3 | "contents": { 4 | "targetPath": "/Users/antony/data/movie_metadata.csv", 5 | "viewParams": { 6 | "aggMap": {}, 7 | "columnFormats": {}, 8 | "defaultFormats": { 9 | "boolean": { 10 | "commas": true, 11 | "decimalPlaces": 0 12 | }, 13 | "integer": { 14 | "commas": true, 15 | "decimalPlaces": 0 16 | }, 17 | "real": { 18 | "commas": true, 19 | "decimalPlaces": 2 20 | }, 21 | "text": { 22 | "urlsAsHyperlinks": true 23 | } 24 | }, 25 | "displayColumns": [ 26 | "gross", 27 | "movie_imdb_link" 28 | ], 29 | "openPaths": { 30 | "_rep": { 31 | "\"USA\"": { 32 | "\"Steven Spielberg\"": {}, 33 | "\"Tim Burton\"": {} 34 | } 35 | } 36 | }, 37 | "pivotLeafColumn": "movie_title", 38 | "showRoot": true, 39 | "sortKey": [ 40 | [ 41 | "gross", 42 | false 43 | ] 44 | ], 45 | "vpivots": [ 46 | "country", 47 | "director_name" 48 | ] 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /packages/tad-app/csv/movie-grossByDirectorWithFilter.tad: -------------------------------------------------------------------------------- 1 | { 2 | "tadFileFormatVersion": 1, 3 | "contents": { 4 | "targetPath": "/Users/antony/data/movie_metadata.csv", 5 | "viewParams": { 6 | "aggMap": {}, 7 | "columnFormats": {}, 8 | "defaultFormats": { 9 | "boolean": { 10 | "commas": true, 11 | "decimalPlaces": 0 12 | }, 13 | "integer": { 14 | "commas": true, 15 | "decimalPlaces": 0 16 | }, 17 | "real": { 18 | "commas": true, 19 | "decimalPlaces": 2 20 | }, 21 | "text": { 22 | "urlsAsHyperlinks": true 23 | } 24 | }, 25 | "displayColumns": [ 26 | "gross", 27 | "movie_imdb_link", 28 | "budget" 29 | ], 30 | "filterExp": { 31 | "expType": "FilterExp", 32 | "op": "AND", 33 | "opArgs": [ 34 | { 35 | "arg": { 36 | "colName": "gross", 37 | "expType": "ColRef" 38 | }, 39 | "expType": "UnaryRelExp", 40 | "op": "NOTNULL" 41 | }, 42 | { 43 | "arg": { 44 | "colName": "budget", 45 | "expType": "ColRef" 46 | }, 47 | "expType": "UnaryRelExp", 48 | "op": "NOTNULL" 49 | }, 50 | { 51 | "expType": "BinRelExp", 52 | "lhs": { 53 | "colName": "budget", 54 | "expType": "ColRef" 55 | }, 56 | "op": "GE", 57 | "rhs": { 58 | "expType": "ConstVal", 59 | "val": 10000000 60 | } 61 | } 62 | ] 63 | }, 64 | "openPaths": { 65 | "_rep": { 66 | "\"Ireland\"": { 67 | "\"Joel Schumacher\"": {}, 68 | "\"Neil Jordan\"": {}, 69 | "\"Steven Soderbergh\"": {} 70 | }, 71 | "\"Sweden\"": {}, 72 | "\"USA\"": { 73 | "\"Steven Spielberg\"": {}, 74 | "\"Tim Burton\"": {} 75 | } 76 | } 77 | }, 78 | "pivotLeafColumn": "movie_title", 79 | "showHiddenCols": false, 80 | "showRoot": true, 81 | "sortKey": [ 82 | [ 83 | "gross", 84 | false 85 | ] 86 | ], 87 | "vpivots": [ 88 | "country", 89 | "director_name" 90 | ] 91 | } 92 | } 93 | } -------------------------------------------------------------------------------- /packages/tad-app/csv/nulltest.csv: -------------------------------------------------------------------------------- 1 | Name,Title,Base,TCOE,Job Family,Union 2 | "Crunican, Grace",General Manager,312461,399921,Executive Management,Non-Represented 3 | "Dugger, Dorothy",General Manager,298700,419661,Executive Management,Non-Represented 4 | "Burrows, Matthew",General Counsel,238269,321543,,Non-Represented 5 | "Oversier, Paul",Assistant General Manager Operations,212647,288160,Executive Management,Non-Represented 6 | "Schroeder, Scott",Controller-Treasurer,208744,287139,Finance & Accounting,Non-Represented 7 | "DeVaughn, Marcia",Deputy General Manager,206914,281548,Executive Management,Non-Represented 8 | "Crespo, Rodolfo",Chief Transportation Officer,170383,238211,Transportation Operations,Non-Represented 9 | "Lee, Thomas",Senior Attorney,165470,224345,,Non-Represented 10 | "Nuetzel, Victoria",Senior Attorney,160289,220683,,Non-Represented 11 | "Ruffa, Frank","Group Manager, Capital Projects",157300,209709,Engineering & Systems Engineering,Non-Represented 12 | "Dupont, Jean-Luc",Group Manager Systems Capital Prog,152434,212535,Engineering & Systems Engineering,Non-Represented 13 | "Hardy, Leonard",Chief Safety Officer,152125,211941,Safety,Non-Represented 14 | "Hui, Sam",Wayside Inspector,77446,128332,"Maintenance, Vehicle & Facilities",SEIU 15 | "Reinhardt, Michael",Structures Inspector,77446,124962,"Maintenance, Vehicle & Facilities",SEIU 16 | "Estrada, Phillip",Vehicle Inspector,77446,123432,"Maintenance, Vehicle & Facilities",SEIU 17 | "Ornellas, Robert",Revenue Protection Guard,75701,127660,Police,BPOA 18 | "Smith, Daniel",Transit Vehicle Mechanic,74068,104260,"Maintenance, Vehicle & Facilities",SEIU 19 | "Smith, Westley",Train Operator,61184,118799,Transportation Operations,ATU 20 | "Hamill, Kerry",Department Manager Gov't & Comm Rel,130035,181955,Public Affairs & Marketing,Non-Represented 21 | "Serrianni, Cheryl",Station Agent,60790,132030,Transportation Operations,ATU 22 | "Nagel, Kevin",Station Agent,60781,105854,Transportation Operations,ATU 23 | "Riggs, Joshua",Station Agent,60771,115104,Transportation Operations,ATU 24 | "Knight, Joshua",Electrician,59817,113775,"Maintenance, Vehicle & Facilities",SEIU 25 | -------------------------------------------------------------------------------- /packages/tad-app/csv/small.csv: -------------------------------------------------------------------------------- 1 | Name,Country,Budget 2 | Joe,USA,1234 3 | Bob,USA,987654 4 | Jane,Canada,3892 5 | Mary,UK,9456 6 | -------------------------------------------------------------------------------- /packages/tad-app/csv/smalltxt.csv: -------------------------------------------------------------------------------- 1 | Name,Country 2 | Joe,USA 3 | Bob,USA 4 | Jane,Canada 5 | Mary,UK 6 | -------------------------------------------------------------------------------- /packages/tad-app/csv/tabtest.csv: -------------------------------------------------------------------------------- 1 | Name,Country,Budget 2 | Joe,USA,1234 3 | Bob Smith,USA,987654 4 | Jane Jenkins,Canada,3892 5 | Mary "Merr E" Jones,UK,9456 6 | -------------------------------------------------------------------------------- /packages/tad-app/csv/test-issue61.csv: -------------------------------------------------------------------------------- 1 | Product #,Product Name,Quantity Invoiced,BP #,BP Name,Line Amount,Date Invoiced,Sales Rep,league,league group,product group,product,Month,Fiscal Year,Acct group,BP type,Rep Group,Team 2 | BCA-BC-877,NBCF LOGO BRACELET,6,W25585,WAL-MART ACCTS PAYABLE,15,12/2/13,S46-31HCM,BCA,BCA,BC,BC-877,12,2014,Walmart,W2,S46, 3 | BCA-BH-862,NBCF BADGE HOLDER,6,W25585,WAL-MART ACCTS PAYABLE,10.5,12/2/13,S46-31HCM,BCA,BCA,BH,BH-862,12,2014,Walmart,W2,S46, 4 | BCA-ER-015,NBCF DANGLER EARRINGS,6,W25585,WAL-MART ACCTS PAYABLE,15,12/2/13,S46-31HCM,BCA,BCA,ER,ER-015,12,2014,Walmart,W2,S46, 5 | BCA-ER-126,NBCF CRYSTAL DANGLER EARRINGS,6,W25585,WAL-MART ACCTS PAYABLE,28.8,12/2/13,S46-31HCM,BCA,BCA,ER,ER-126,12,2014,Walmart,W2,S46, 6 | BCA-FJ-830,NBCF CRYSTAL CIRCLE NECKLACE,6,W25585,WAL-MART ACCTS PAYABLE,28.8,12/2/13,S46-31HCM,BCA,BCA,FJ,FJ-830,12,2014,Walmart,W2,S46, 7 | BCA-KT-147,NBCF CARABINER LANYARD KEY TAG,12,W25585,WAL-MART ACCTS PAYABLE,30,12/2/13,S46-03TR,BCA,BCA,KT,KT-147,12,2014,Walmart,W2,S46, 8 | BCA-KT-147,NBCF CARABINER LANYARD KEY TAG,6,W25585,WAL-MART ACCTS PAYABLE,15,12/2/13,S46-31HCM,BCA,BCA,KT,KT-147,12,2014,Walmart,W2,S46, 9 | BCA-LN-095-BK,NBCF BLACK LANYARD,12,W25585,WAL-MART ACCTS PAYABLE,33.6,12/2/13,S46-03TR,BCA,BCA,LN,LN-095,12,2014,Walmart,W2,S46,BK 10 | BCA-LN-095-BK,NBCF BLACK LANYARD,12,W25585,WAL-MART ACCTS PAYABLE,33.6,12/2/13,S46-13PK,BCA,BCA,LN,LN-095,12,2014,Walmart,W2,S46,BK 11 | BCA-LN-095-BK,NBCF BLACK LANYARD,12,W25585,WAL-MART ACCTS PAYABLE,33.6,12/2/13,S46-31HCM,BCA,BCA,LN,LN-095,12,2014,Walmart,W2,S46,BK 12 | BCA-LN-095-BK,NBCF BLACK LANYARD,24,W25585,WAL-MART ACCTS PAYABLE,67.2,12/2/13,S46-16SH,BCA,BCA,LN,LN-095,12,2014,Walmart,W2,S46,BK 13 | BCA-LN-095-P,NBCF PINK LANYARD,6,W25585,WAL-MART ACCTS PAYABLE,16.8,12/2/13,S46-16SH,BCA,BCA,LN,LN-095,12,2014,Walmart,W2,S46,P 14 | BCA-LN-095-P,NBCF PINK LANYARD,12,W25585,WAL-MART ACCTS PAYABLE,33.6,12/2/13,S46-13PK,BCA,BCA,LN,LN-095,12,2014,Walmart,W2,S46,P 15 | BCA-LN-095-P,NBCF PINK LANYARD,12,W25585,WAL-MART ACCTS PAYABLE,33.6,12/2/13,S46-31HCM,BCA,BCA,LN,LN-095,12,2014,Walmart,W2,S46,P -------------------------------------------------------------------------------- /packages/tad-app/examples/movie-grossByCountryDirector.tad: -------------------------------------------------------------------------------- 1 | { 2 | "tadFileFormatVersion": 1, 3 | "contents": { 4 | "targetPath": "/Users/antony/data/movie_metadata.csv", 5 | "viewParams": { 6 | "aggMap": {}, 7 | "columnFormats": {}, 8 | "defaultFormats": { 9 | "boolean": { 10 | "commas": true, 11 | "decimalPlaces": 0 12 | }, 13 | "integer": { 14 | "commas": true, 15 | "decimalPlaces": 0 16 | }, 17 | "real": { 18 | "commas": true, 19 | "decimalPlaces": 2 20 | }, 21 | "text": { 22 | "urlsAsHyperlinks": true 23 | } 24 | }, 25 | "displayColumns": [ 26 | "gross", 27 | "movie_imdb_link" 28 | ], 29 | "openPaths": { 30 | "_rep": { 31 | "\"USA\"": { 32 | "\"Steven Spielberg\"": {}, 33 | "\"Tim Burton\"": {} 34 | } 35 | } 36 | }, 37 | "pivotLeafColumn": "movie_title", 38 | "showRoot": true, 39 | "sortKey": [ 40 | [ 41 | "gross", 42 | false 43 | ] 44 | ], 45 | "vpivots": [ 46 | "country", 47 | "director_name" 48 | ] 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /packages/tad-app/examples/movie-grossByDirectorWithFilter.tad: -------------------------------------------------------------------------------- 1 | { 2 | "tadFileFormatVersion": 1, 3 | "contents": { 4 | "targetPath": "/Users/antony/data/movie_metadata.csv", 5 | "viewParams": { 6 | "aggMap": {}, 7 | "columnFormats": {}, 8 | "defaultFormats": { 9 | "boolean": { 10 | "commas": true, 11 | "decimalPlaces": 0 12 | }, 13 | "integer": { 14 | "commas": true, 15 | "decimalPlaces": 0 16 | }, 17 | "real": { 18 | "commas": true, 19 | "decimalPlaces": 2 20 | }, 21 | "text": { 22 | "urlsAsHyperlinks": true 23 | } 24 | }, 25 | "displayColumns": [ 26 | "gross", 27 | "movie_imdb_link", 28 | "budget" 29 | ], 30 | "filterExp": { 31 | "expType": "FilterExp", 32 | "op": "AND", 33 | "opArgs": [ 34 | { 35 | "arg": { 36 | "colName": "gross", 37 | "expType": "ColRef" 38 | }, 39 | "expType": "UnaryRelExp", 40 | "op": "NOTNULL" 41 | }, 42 | { 43 | "arg": { 44 | "colName": "budget", 45 | "expType": "ColRef" 46 | }, 47 | "expType": "UnaryRelExp", 48 | "op": "NOTNULL" 49 | }, 50 | { 51 | "expType": "BinRelExp", 52 | "lhs": { 53 | "colName": "budget", 54 | "expType": "ColRef" 55 | }, 56 | "op": "GE", 57 | "rhs": { 58 | "expType": "ConstVal", 59 | "val": 10000000 60 | } 61 | } 62 | ] 63 | }, 64 | "openPaths": { 65 | "_rep": { 66 | "\"Ireland\"": { 67 | "\"Joel Schumacher\"": {}, 68 | "\"Neil Jordan\"": {}, 69 | "\"Steven Soderbergh\"": {} 70 | }, 71 | "\"Sweden\"": {}, 72 | "\"USA\"": { 73 | "\"Steven Spielberg\"": {}, 74 | "\"Tim Burton\"": {} 75 | } 76 | } 77 | }, 78 | "pivotLeafColumn": "movie_title", 79 | "showHiddenCols": false, 80 | "showRoot": true, 81 | "sortKey": [ 82 | [ 83 | "gross", 84 | false 85 | ] 86 | ], 87 | "vpivots": [ 88 | "country", 89 | "director_name" 90 | ] 91 | } 92 | } 93 | } -------------------------------------------------------------------------------- /packages/tad-app/html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Tad 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /packages/tad-app/html/userdocs/css/main.css: -------------------------------------------------------------------------------- 1 | body{ 2 | font-family: 'Lato', sans-serif; 3 | font-size: 17px; 4 | color: #35556f; 5 | } 6 | header{ 7 | background-image: -moz-linear-gradient( 80deg, rgb(10,110,191) 0%, rgb(29,139,224) 100%); 8 | background-image: -webkit-linear-gradient( 80deg, rgb(10,110,191) 0%, rgb(29,139,224) 100%); 9 | background-image: -ms-linear-gradient( 80deg, rgb(10,110,191) 0%, rgb(29,139,224) 100%); 10 | color: #FFF; 11 | text-align: center; 12 | padding-bottom: 72px; 13 | } 14 | header .navbar-nav > li > a{ 15 | padding-top: 22px; 16 | } 17 | .navbar-brand img{ 18 | max-width: 70px; 19 | } 20 | h1{ 21 | font-size: 50px; 22 | color: #FFF; 23 | font-weight: 300; 24 | margin-top:100px; 25 | } 26 | h1.ornament{ 27 | background-image: url(../images/ornament.png); 28 | background-repeat: no-repeat; 29 | background-position: bottom center; 30 | padding-bottom: 51px; 31 | } 32 | .screenshot-holder{ 33 | margin-top: 48px; 34 | margin-bottom:44px; 35 | } 36 | .screenshot-holder img{ 37 | max-width: 100%; 38 | box-shadow: 0px 10px 25px 4px rgba(18, 49, 74, 0.25); 39 | } 40 | .btn-default{ 41 | color: #0072ce; 42 | font-size: 22px; 43 | margin-top:10px; 44 | border-radius: 16px; 45 | font-weight: 700; 46 | min-width: 217px; 47 | } 48 | .navbar-brand{ 49 | font-size: 40px; 50 | font-weight: 900; 51 | } 52 | main{ 53 | text-align: center; 54 | padding-bottom: 60px; 55 | } 56 | main p strong{ 57 | color:#0072ce; 58 | } 59 | main .specs-holder img{ 60 | max-width: 55px; 61 | } 62 | main .specs-holder{ 63 | margin-top:60px; 64 | padding: 0 70px; 65 | } 66 | main .specs-holder p{ 67 | margin-top:28px; 68 | line-height: 28px; 69 | } 70 | footer{ 71 | text-align: center; 72 | } 73 | footer p{ 74 | font-size: 14px; 75 | } 76 | footer .container{ 77 | border-top:1px solid #cce3f5; 78 | padding: 25px 15px; 79 | } 80 | footer p a{ 81 | font-weight: bold; 82 | color:#35556f; 83 | } 84 | @media (max-width:1199px){ 85 | main .specs-holder{ 86 | padding: 0 20px; 87 | } 88 | } 89 | @media (max-width:991px){ 90 | body{ 91 | font-size: 14px; 92 | } 93 | h1{ 94 | font-size: 35px; 95 | } 96 | p{ 97 | font-size:14px; 98 | } 99 | 100 | } 101 | @media (max-width:767px){ 102 | .navbar-header{ 103 | min-height: 60px; 104 | } 105 | .navbar-default .navbar-toggle{ 106 | background: #FFF; 107 | } 108 | .navbar-default .navbar-toggle .icon-bar{ 109 | background: #1d8be0; 110 | } 111 | header .navbar-nav > li > a{ 112 | font-size: 18px; 113 | } 114 | } -------------------------------------------------------------------------------- /packages/tad-app/html/userdocs/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonycourtney/tad/272ffa0092024cd57b82fc813af1f8c6568bc3f0/packages/tad-app/html/userdocs/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /packages/tad-app/html/userdocs/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonycourtney/tad/272ffa0092024cd57b82fc813af1f8c6568bc3f0/packages/tad-app/html/userdocs/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /packages/tad-app/html/userdocs/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonycourtney/tad/272ffa0092024cd57b82fc813af1f8c6568bc3f0/packages/tad-app/html/userdocs/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /packages/tad-app/html/userdocs/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonycourtney/tad/272ffa0092024cd57b82fc813af1f8c6568bc3f0/packages/tad-app/html/userdocs/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /packages/tad-app/html/userdocs/images/tad-data-sources.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonycourtney/tad/272ffa0092024cd57b82fc813af1f8c6568bc3f0/packages/tad-app/html/userdocs/images/tad-data-sources.png -------------------------------------------------------------------------------- /packages/tad-app/html/userdocs/images/tad-quickstart-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonycourtney/tad/272ffa0092024cd57b82fc813af1f8c6568bc3f0/packages/tad-app/html/userdocs/images/tad-quickstart-1.png -------------------------------------------------------------------------------- /packages/tad-app/html/userdocs/images/tad-quickstart-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonycourtney/tad/272ffa0092024cd57b82fc813af1f8c6568bc3f0/packages/tad-app/html/userdocs/images/tad-quickstart-2.png -------------------------------------------------------------------------------- /packages/tad-app/html/userdocs/images/tad-quickstart-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonycourtney/tad/272ffa0092024cd57b82fc813af1f8c6568bc3f0/packages/tad-app/html/userdocs/images/tad-quickstart-3.png -------------------------------------------------------------------------------- /packages/tad-app/html/userdocs/images/tad-quickstart-filter-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonycourtney/tad/272ffa0092024cd57b82fc813af1f8c6568bc3f0/packages/tad-app/html/userdocs/images/tad-quickstart-filter-1.png -------------------------------------------------------------------------------- /packages/tad-app/html/userdocs/images/tad-quickstart-filter-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonycourtney/tad/272ffa0092024cd57b82fc813af1f8c6568bc3f0/packages/tad-app/html/userdocs/images/tad-quickstart-filter-2.png -------------------------------------------------------------------------------- /packages/tad-app/html/userdocs/images/tad-quickstart-filter-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonycourtney/tad/272ffa0092024cd57b82fc813af1f8c6568bc3f0/packages/tad-app/html/userdocs/images/tad-quickstart-filter-3.png -------------------------------------------------------------------------------- /packages/tad-app/res/AppIcon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonycourtney/tad/272ffa0092024cd57b82fc813af1f8c6568bc3f0/packages/tad-app/res/AppIcon.icns -------------------------------------------------------------------------------- /packages/tad-app/res/AppIcon1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonycourtney/tad/272ffa0092024cd57b82fc813af1f8c6568bc3f0/packages/tad-app/res/AppIcon1024.png -------------------------------------------------------------------------------- /packages/tad-app/res/blue_external_drive.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonycourtney/tad/272ffa0092024cd57b82fc813af1f8c6568bc3f0/packages/tad-app/res/blue_external_drive.icns -------------------------------------------------------------------------------- /packages/tad-app/res/entitlements.mac.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.cs.allow-unsigned-executable-memory 6 | 7 | com.apple.security.cs.allow-jit 8 | 9 | com.apple.security.cs.disable-library-validation 10 | 11 | 12 | -------------------------------------------------------------------------------- /packages/tad-app/runtad.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # script for running Tad executable in foreground for debugging / testing 4 | ./dist/mac/tad.app/Contents/MacOS/tad -f $@ -------------------------------------------------------------------------------- /packages/tad-app/src/app.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | margin: 0; 4 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 5 | font-weight: 400; 6 | font-size: 8pt; 7 | height: 100%; 8 | overflow: hidden; 9 | } 10 | -------------------------------------------------------------------------------- /packages/tad-app/src/electronClient.ts: -------------------------------------------------------------------------------- 1 | import log from "loglevel"; 2 | import * as reltab from "reltab"; 3 | import * as electron from "electron"; 4 | import { ipcRenderer } from "electron"; 5 | import { TransportClient } from "reltab"; 6 | 7 | // TODO: Exception handling! 8 | export class ElectronTransportClient implements TransportClient { 9 | invoke(functionName: string, req: any): Promise { 10 | return ipcRenderer.invoke(functionName, req); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/tad-app/src/openParams.ts: -------------------------------------------------------------------------------- 1 | import { DataSourcePath } from "reltab"; 2 | 3 | // TODO: various database files / tables 4 | export type OpenType = "fspath" | "tad" | "dspath" | "empty"; 5 | 6 | // fspath -- a path to a data file (CSV, TSV, parquet, etc) or directory 7 | export interface OpenFSPath { 8 | openType: "fspath"; 9 | path: string; // filesystem path 10 | } 11 | 12 | export interface OpenDSPath { 13 | openType: "dspath"; 14 | dsPath: DataSourcePath; 15 | } 16 | 17 | export interface OpenTad { 18 | openType: "tad"; 19 | fileContents: string; 20 | fileBaseName: string; // for title 21 | } 22 | 23 | export interface OpenEmpty { 24 | openType: "empty"; 25 | } 26 | 27 | export type OpenParams = OpenFSPath | OpenDSPath | OpenTad | OpenEmpty; 28 | -------------------------------------------------------------------------------- /packages/tad-app/tools/afterPack.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const util = require("util"); 3 | const exec = util.promisify(require("child_process").exec); 4 | const copy = require("recursive-copy"); 5 | 6 | //const WIN_SSL_DIR = '../../../openssl-1.1'; 7 | 8 | async function afterPack(context) { 9 | console.log("afterPack hook called:", context.packager.platform.name); 10 | console.log('skipping OpenSSL bundling (no longer required by duckdb)'); 11 | /* 12 | if (context.packager.platform.name === "mac") { 13 | console.log("On Mac, running dylib-fixup script:"); 14 | const { appOutDir } = context; 15 | const addonPath = path.join( 16 | appOutDir, 17 | "Tad.app/Contents/Resources/app.asar.unpacked/node_modules/duckdb/lib/binding/duckdb.node" 18 | ); 19 | const { stdout, stderr } = await exec( 20 | `./tools/dylib-fixup.sh ${addonPath}` 21 | ); 22 | console.log(stdout); 23 | console.error(stderr); 24 | } 25 | if (context.packager.platform.name === "windows") { 26 | const WIN_SSL_DIR = process.env.OPENSSL_ROOT_DIR; 27 | console.log("On Windows, packaging OPENSSL dlls from ", WIN_SSL_DIR); 28 | const { appOutDir } = context; 29 | const duckDbTargetDir = path.join( 30 | appOutDir, 31 | "resources/app.asar.unpacked/node_modules/duckdb/lib/binding" 32 | ); 33 | const sslBinDir = path.join(WIN_SSL_DIR, "bin"); 34 | const results = await copy(sslBinDir, duckDbTargetDir); 35 | console.info( 36 | "afterPack: Copied " + 37 | results.length + 38 | " files from SSL bin dir to target" 39 | ); 40 | } 41 | */ 42 | } 43 | 44 | exports.default = afterPack; 45 | -------------------------------------------------------------------------------- /packages/tad-app/tools/convert.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -x 3 | files=$(ls app/*.js) 4 | for f in $files 5 | do 6 | bnm=$(basename -s .js $f) 7 | babel --plugins babel-plugin-flow-to-typescript $f -o 'app/'$bnm'.ts' 8 | done 9 | -------------------------------------------------------------------------------- /packages/tad-app/tools/dylib-fixup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | echo "dylib-fixup: Fixing up dylibs in " $@ 4 | BREW_PREFIX=$(brew --prefix) 5 | install_name_tool -change $BREW_PREFIX/opt/openssl@3/lib/libcrypto.3.dylib @rpath/libcrypto.3.dylib $@ 6 | install_name_tool -change $BREW_PREFIX/opt/openssl@3/lib/libssl.3.dylib @rpath/libssl.3.dylib $@ 7 | targetDir=$(dirname $@) 8 | # duckdbDylib=$targetDir/libduckdb.dylib 9 | # install_name_tool -change $BREW_PREFIX/opt/openssl@3/lib/libcrypto.3.dylib @rpath/libcrypto.3.dylib $duckdbDylib 10 | # install_name_tool -change $BREW_PREFIX/opt/openssl@3/lib/libssl.3.dylib @rpath/libssl.3.dylib $duckdbDylib 11 | cp $BREW_PREFIX/opt/openssl@3/lib/libcrypto.3.dylib $targetDir 12 | cp $BREW_PREFIX/opt/openssl@3/lib/libssl.3.dylib $targetDir 13 | # finally, fix up libssl's ref to libcrypto: 14 | sslDylib=$targetDir/libssl.3.dylib 15 | OPENSSL_VERSION=$(brew info --json openssl |jq .[0].installed[0].version |tr -d '"') 16 | # sigh -- make this work for a variety of openssl versions: 17 | install_name_tool -change $BREW_PREFIX/Cellar/openssl@3/$OPENSSL_VERSION/lib/libcrypto.3.dylib @rpath/libcrypto.3.dylib $sslDylib 18 | echo "dylib-fixup: done." 19 | -------------------------------------------------------------------------------- /packages/tad-app/tools/notarize.js: -------------------------------------------------------------------------------- 1 | const { notarize } = require("@electron/notarize"); 2 | 3 | exports.default = async function notarizing(context) { 4 | const { electronPlatformName, appOutDir } = context; 5 | if (electronPlatformName !== "darwin") { 6 | return; 7 | } 8 | 9 | const appName = context.packager.appInfo.productFilename; 10 | const appleId = process.env.APPLEID; 11 | if (!appleId) { 12 | console.error( 13 | "notarize.js: APPLEID env var not set. Please set APPLEID and APPLEIDPASS env vars to your Apple ID and password" 14 | ); 15 | throw new Error("notarize: Apple ID credentials not set"); 16 | } 17 | 18 | return await notarize({ 19 | appBundleId: "com.antonycourtney.tad", 20 | appPath: `${appOutDir}/${appName}.app`, 21 | appleId, 22 | appleIdPassword: process.env.APPLEIDPASS, 23 | teamId: "VPS8BQAV8D", 24 | }); 25 | }; 26 | -------------------------------------------------------------------------------- /packages/tad-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist", 4 | "declaration": true, 5 | "target": "es5", 6 | "lib": ["dom", "dom.iterable", "esnext"], 7 | "allowJs": true, 8 | "skipLibCheck": true, 9 | "esModuleInterop": true, 10 | "allowSyntheticDefaultImports": true, 11 | "strict": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "module": "esnext", 14 | "moduleResolution": "node", 15 | "resolveJsonModule": true, 16 | "noEmit": false, 17 | "sourceMap": true, 18 | "jsx": "react", 19 | "downlevelIteration": true, 20 | "rootDirs": ["src/ts", "app"] 21 | }, 22 | "include": ["./src/*", "./app/*"] 23 | } 24 | -------------------------------------------------------------------------------- /packages/tadviewer/html/css/main.css: -------------------------------------------------------------------------------- 1 | body{ 2 | font-family: 'Lato', sans-serif; 3 | font-size: 17px; 4 | color: #35556f; 5 | } 6 | header{ 7 | background-image: -moz-linear-gradient( 80deg, rgb(10,110,191) 0%, rgb(29,139,224) 100%); 8 | background-image: -webkit-linear-gradient( 80deg, rgb(10,110,191) 0%, rgb(29,139,224) 100%); 9 | background-image: -ms-linear-gradient( 80deg, rgb(10,110,191) 0%, rgb(29,139,224) 100%); 10 | color: #FFF; 11 | text-align: center; 12 | padding-bottom: 72px; 13 | } 14 | header .navbar-nav > li > a{ 15 | padding-top: 22px; 16 | } 17 | .navbar-brand img{ 18 | max-width: 70px; 19 | } 20 | h1{ 21 | font-size: 50px; 22 | color: #FFF; 23 | font-weight: 300; 24 | margin-top:100px; 25 | } 26 | h1.ornament{ 27 | background-image: url(../images/ornament.png); 28 | background-repeat: no-repeat; 29 | background-position: bottom center; 30 | padding-bottom: 51px; 31 | } 32 | .screenshot-holder{ 33 | margin-top: 48px; 34 | margin-bottom:44px; 35 | } 36 | .screenshot-holder img{ 37 | max-width: 100%; 38 | box-shadow: 0px 10px 25px 4px rgba(18, 49, 74, 0.25); 39 | } 40 | .btn-default{ 41 | color: #0072ce; 42 | font-size: 22px; 43 | margin-top:10px; 44 | border-radius: 16px; 45 | font-weight: 700; 46 | min-width: 217px; 47 | } 48 | .navbar-brand{ 49 | font-size: 40px; 50 | font-weight: 900; 51 | } 52 | main{ 53 | text-align: center; 54 | padding-bottom: 60px; 55 | } 56 | main p strong{ 57 | color:#0072ce; 58 | } 59 | main .specs-holder img{ 60 | max-width: 55px; 61 | } 62 | main .specs-holder{ 63 | margin-top:60px; 64 | padding: 0 70px; 65 | } 66 | main .specs-holder p{ 67 | margin-top:28px; 68 | line-height: 28px; 69 | } 70 | footer{ 71 | text-align: center; 72 | } 73 | footer p{ 74 | font-size: 14px; 75 | } 76 | footer .container{ 77 | border-top:1px solid #cce3f5; 78 | padding: 25px 15px; 79 | } 80 | footer p a{ 81 | font-weight: bold; 82 | color:#35556f; 83 | } 84 | @media (max-width:1199px){ 85 | main .specs-holder{ 86 | padding: 0 20px; 87 | } 88 | } 89 | @media (max-width:991px){ 90 | body{ 91 | font-size: 14px; 92 | } 93 | h1{ 94 | font-size: 35px; 95 | } 96 | p{ 97 | font-size:14px; 98 | } 99 | 100 | } 101 | @media (max-width:767px){ 102 | .navbar-header{ 103 | min-height: 60px; 104 | } 105 | .navbar-default .navbar-toggle{ 106 | background: #FFF; 107 | } 108 | .navbar-default .navbar-toggle .icon-bar{ 109 | background: #1d8be0; 110 | } 111 | header .navbar-nav > li > a{ 112 | font-size: 18px; 113 | } 114 | } -------------------------------------------------------------------------------- /packages/tadviewer/html/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonycourtney/tad/272ffa0092024cd57b82fc813af1f8c6568bc3f0/packages/tadviewer/html/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /packages/tadviewer/html/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonycourtney/tad/272ffa0092024cd57b82fc813af1f8c6568bc3f0/packages/tadviewer/html/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /packages/tadviewer/html/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonycourtney/tad/272ffa0092024cd57b82fc813af1f8c6568bc3f0/packages/tadviewer/html/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /packages/tadviewer/html/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonycourtney/tad/272ffa0092024cd57b82fc813af1f8c6568bc3f0/packages/tadviewer/html/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /packages/tadviewer/html/images/tad-quickstart-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonycourtney/tad/272ffa0092024cd57b82fc813af1f8c6568bc3f0/packages/tadviewer/html/images/tad-quickstart-1.png -------------------------------------------------------------------------------- /packages/tadviewer/html/images/tad-quickstart-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonycourtney/tad/272ffa0092024cd57b82fc813af1f8c6568bc3f0/packages/tadviewer/html/images/tad-quickstart-2.png -------------------------------------------------------------------------------- /packages/tadviewer/html/images/tad-quickstart-filter-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonycourtney/tad/272ffa0092024cd57b82fc813af1f8c6568bc3f0/packages/tadviewer/html/images/tad-quickstart-filter-1.png -------------------------------------------------------------------------------- /packages/tadviewer/html/images/tad-quickstart-filter-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonycourtney/tad/272ffa0092024cd57b82fc813af1f8c6568bc3f0/packages/tadviewer/html/images/tad-quickstart-filter-2.png -------------------------------------------------------------------------------- /packages/tadviewer/html/images/tad-quickstart-filter-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonycourtney/tad/272ffa0092024cd57b82fc813af1f8c6568bc3f0/packages/tadviewer/html/images/tad-quickstart-filter-3.png -------------------------------------------------------------------------------- /packages/tadviewer/less/activityBar.less: -------------------------------------------------------------------------------- 1 | .activityBar { 2 | background-color: #efefef; 3 | display: inherit; 4 | width: 50px; 5 | min-width: 50px; 6 | flex: 0 0 auto; 7 | flex-direction: column; 8 | align-items: center; 9 | height: 100%; 10 | overflow: none; 11 | } 12 | 13 | .activityBar > button { 14 | margin-left: 5px; 15 | margin-right: 5px; 16 | margin-top: 15px; 17 | margin-bottom: 10px; 18 | } 19 | -------------------------------------------------------------------------------- /packages/tadviewer/less/columnList.less: -------------------------------------------------------------------------------- 1 | .column-list { 2 | border: 1px solid #c0c0c0; 3 | background: #ffffff; 4 | margin-bottom: 5px; 5 | } 6 | 7 | .column-list-header { 8 | border-bottom: 1px solid #c0c0c0; 9 | } 10 | 11 | .column-list-body { 12 | height: 150px; 13 | overflow-y: auto; 14 | overflow-x: hidden; 15 | } 16 | 17 | .column-list .col-colName { 18 | width: 195px; 19 | text-overflow: ellipsis; 20 | white-space: nowrap; 21 | overflow: hidden; 22 | } 23 | 24 | .column-list td { 25 | padding-top: 4px; 26 | padding-bottom: 4px; 27 | } 28 | 29 | .column-list-th { 30 | text-align: left; 31 | } 32 | -------------------------------------------------------------------------------- /packages/tadviewer/less/columnSelector.less: -------------------------------------------------------------------------------- 1 | .column-selector { 2 | border: 1px solid #c0c0c0; 3 | font-size: 8pt; 4 | } 5 | 6 | .column-selector-table { 7 | background-color: #ffffff; 8 | table-layout: fixed; 9 | width: 298px; 10 | margin-bottom: 0; 11 | } 12 | 13 | .column-selector-header { 14 | border-bottom: 1px solid #c0c0c0; 15 | } 16 | 17 | .column-selector-body { 18 | height: 350px; 19 | overflow-y: auto; 20 | overflow-x: hidden; 21 | } 22 | 23 | .column-selector .col-colName { 24 | width: 42%; 25 | text-overflow: ellipsis; 26 | white-space: nowrap; 27 | overflow: hidden; 28 | } 29 | 30 | .column-selector .col-colType { 31 | width: 23%; 32 | } 33 | 34 | .column-selector .col-check { 35 | width: 12%; 36 | text-align: center; 37 | padding-left: 2px; 38 | padding-right: 2px; 39 | } 40 | 41 | table.column-selector-table td { 42 | padding-top: 4px; 43 | padding-bottom: 4px; 44 | } 45 | 46 | .column-selector-th { 47 | border-bottom: 0; 48 | } 49 | .column-selector thead { 50 | border-bottom: 1px solid #c0c0c0; 51 | } 52 | 53 | .column-selector .all-row { 54 | background-color: #f0f0f0; 55 | } 56 | 57 | .column-selector .col-check:last-child { 58 | padding-right: 16px; 59 | } 60 | 61 | .col-colName-all { 62 | font-style: italic; 63 | font-weight: bolder; 64 | } 65 | -------------------------------------------------------------------------------- /packages/tadviewer/less/delayedCalcFooter.less: -------------------------------------------------------------------------------- 1 | .delayed-calc-footer { 2 | display: flex; 3 | flex-direction: row; 4 | justify-content: flex-end; 5 | align-items: center; 6 | margin-bottom: 10px; 7 | } 8 | 9 | .delayed-calc-footer > button { 10 | margin-left: 5px; 11 | margin-right: 5px; 12 | margin-top: 10px; 13 | margin-bottom: 10px; 14 | } 15 | -------------------------------------------------------------------------------- /packages/tadviewer/less/filterEditor.less: -------------------------------------------------------------------------------- 1 | @filter-row-value-width: 470px; 2 | @filter-row-width: 900px; 3 | .filter-editor { 4 | margin-top: 10px; 5 | padding-left: 16px; 6 | padding-right: 16px; 7 | overflow: hidden; 8 | flex: 0 0 auto; 9 | display: flex; 10 | justify-content: flex-start; 11 | flex-direction: column; 12 | } 13 | 14 | .filter-editor-filter-pane { 15 | display: flex; 16 | justify-content: flex-start; 17 | flex-direction: column; 18 | overflow: hidden; 19 | flex: 0 0 auto; 20 | display: flex; 21 | justify-content: flex-start; 22 | flex-direction: column; 23 | } 24 | 25 | .filter-editor-scroll-pane { 26 | display: flex; 27 | height: 200px; 28 | width: @filter-row-width; 29 | flex: 0 0 auto; 30 | border: 1px solid #cacaca; 31 | justify-content: flex-start; 32 | flex-direction: column; 33 | overflow: auto; 34 | } 35 | 36 | .filter-editor-select-row { 37 | display: flex; 38 | justify-content: flex-start; 39 | flex-direction: row; 40 | flex: 0 0 auto; 41 | } 42 | 43 | .filter-editor-row { 44 | display: flex; 45 | justify-content: flex-start; 46 | flex-direction: row; 47 | padding: 5px; 48 | flex: 0 0 auto; 49 | padding-left: 10px; 50 | height: fit-content; 51 | } 52 | 53 | .filter-editor-row-predicate { 54 | display: flex; 55 | justify-content: flex-start; 56 | flex-direction: row; 57 | height: fit-content; 58 | } 59 | 60 | .filter-editor-value { 61 | width: @filter-row-value-width; 62 | } 63 | 64 | /* 65 | .filter-editor-row-predicate > * { 66 | margin-right: 8px; 67 | } 68 | */ 69 | 70 | .filter-row-col-select, 71 | .filter-row-op-select { 72 | margin-right: 8px; 73 | flex: 0 0 auto; 74 | } 75 | 76 | .filter-row-col-select, 77 | .filter-row-col-select select { 78 | width: 200px; 79 | } 80 | 81 | .filter-row-op-select, 82 | .filter-row-op-select select { 83 | width: 170px; 84 | } 85 | 86 | .filter-editor-edit-section { 87 | flex: 1 0 auto; 88 | overflow: auto; 89 | } 90 | -------------------------------------------------------------------------------- /packages/tadviewer/less/footer.less: -------------------------------------------------------------------------------- 1 | @footer-collapsed-height: 30px; 2 | @footer-expanded-height: 320px; 3 | 4 | .footer { 5 | background-color: #efefef; 6 | border: 1px solid #bababa; 7 | padding-top: 4px; 8 | padding-bottom: 4px; 9 | font-size: var(--tad-footer-font-size, 14px); 10 | flex: 0 0 auto; 11 | display: flex; 12 | justify-content: flex-start; 13 | flex-direction: column; 14 | margin: 0; 15 | transition-duration: 0.3s; 16 | overflow: hidden; 17 | } 18 | 19 | .footer-collapsed { 20 | height: @footer-collapsed-height; 21 | } 22 | 23 | .footer-expanded { 24 | height: @footer-expanded-height; 25 | } 26 | 27 | .footer-top-row { 28 | height: @footer-collapsed-height; 29 | display: flex; 30 | flex-direction: row; 31 | padding-left: 16px; 32 | padding-right: 16px; 33 | justify-content: space-between; 34 | } 35 | 36 | .footer-filter-block { 37 | display: flex; 38 | flex-direction: row; 39 | justify-content: flex-start; 40 | max-width: 600px; 41 | } 42 | 43 | .footer-right-block { 44 | display: flex; 45 | flex-direction: row; 46 | gap: 8px; 47 | justify-content: flex-start; 48 | max-width: 600px; 49 | } 50 | 51 | .filter-summary { 52 | margin-left: 8px; 53 | max-width: 550px; 54 | text-overflow: ellipsis; 55 | overflow: hidden; 56 | white-space: nowrap; 57 | } 58 | -------------------------------------------------------------------------------- /packages/tadviewer/less/modal.less: -------------------------------------------------------------------------------- 1 | .modal-overlay { 2 | position: fixed; 3 | top: 0; 4 | left: 0; 5 | background: rgba(0,0,0,0.6); 6 | z-index: 5; 7 | width: 100%; 8 | height: 100%; 9 | display: flex; 10 | } 11 | .loading-modal-overlay { 12 | position: absolute; 13 | top: 0; 14 | left: 0; 15 | background: rgba(0,0,0,0.2); 16 | z-index: 5; 17 | width: 100%; 18 | height: 100%; 19 | display: flex; 20 | } 21 | 22 | .loading-modal-container { 23 | width: 300; 24 | position: relative; 25 | z-index: 10; 26 | background: #fff; 27 | margin: auto; 28 | display: flex; 29 | flex-direction: column; 30 | } 31 | .modal-container { 32 | width: 300; 33 | position: relative; 34 | z-index: 10; 35 | border-radius: 3; 36 | background: #fff; 37 | margin: auto; 38 | border: 1px solid #bababa; 39 | display: flex; 40 | flex-direction: column; 41 | } 42 | .modal-body-container { 43 | display: flex; 44 | minHeight: 300; 45 | maxHeight: 400; 46 | overflow: auto; 47 | flexDirection: column 48 | } 49 | -------------------------------------------------------------------------------- /packages/tadviewer/less/sidebar.less: -------------------------------------------------------------------------------- 1 | .sidebar { 2 | background-color: #efefef; 3 | display: inherit; 4 | flex: 0 0 auto; 5 | height: 100%; 6 | transition-duration: 0.3s; 7 | overflow: none; 8 | } 9 | 10 | .sidebar-collapsed { 11 | width: 0px; 12 | min-width: 0px; 13 | } 14 | 15 | .sidebar-expanded { 16 | width: 340px; 17 | min-width: 340px; 18 | } 19 | 20 | .sidebar-content { 21 | visibility: hidden; 22 | height: 100%; 23 | width: 340px; 24 | min-width: 340px; 25 | overflow-y: auto; 26 | overflow-x: hidden; 27 | } 28 | 29 | .sidebar-content-inner { 30 | margin-left: 8px; 31 | margin-top: 8px; 32 | margin-right: 16px; 33 | width: 300px; 34 | } 35 | 36 | .sidebar-placeholder { 37 | /* 38 | position: absolute; 39 | left: 0; 40 | top: 0; 41 | */ 42 | margin-left: 8px; 43 | margin-top: 8px; 44 | } 45 | 46 | .addl-col-props { 47 | margin-top: 40px; 48 | } 49 | 50 | .sidebar-expanded > .sidebar-placeholder { 51 | visibility: hidden; 52 | } 53 | 54 | .sidebar-expanded > .sidebar-content { 55 | visibility: visible; 56 | } 57 | 58 | .show-root-label { 59 | margin-left: 5px; 60 | } 61 | 62 | .root-check-group { 63 | margin-left: 8px; 64 | } 65 | 66 | .format-col-select { 67 | max-width: 140px; 68 | } 69 | 70 | .format-subpanel { 71 | margin-top: 8px; 72 | } 73 | 74 | .format-decimals { 75 | max-width: 80px; 76 | } 77 | 78 | .format-decimals .pt-input-group { 79 | width: 40px; 80 | max-width: 80px; 81 | } 82 | 83 | .num-format-panel input { 84 | width: 40px; 85 | } 86 | -------------------------------------------------------------------------------- /packages/tadviewer/less/singleColumnSelect.less: -------------------------------------------------------------------------------- 1 | .pivot-leaf-select { 2 | margin-top: 8px; 3 | margin-left: 4px; 4 | font-size: 10pt; 5 | } 6 | 7 | .scs-select { 8 | width: 160px; 9 | margin-left: 8px; 10 | } 11 | -------------------------------------------------------------------------------- /packages/tadviewer/less/tadviewer.less: -------------------------------------------------------------------------------- 1 | @import "~normalize.css"; 2 | @import "~@blueprintjs/core/lib/less/variables.less"; 3 | @import "~@blueprintjs/core/lib/css/blueprint.css"; 4 | @import "~@blueprintjs/icons/lib/css/blueprint-icons.css"; 5 | @import "~@blueprintjs/popover2/lib/css/blueprint-popover2.css"; 6 | 7 | .tad-app-pane { 8 | font-family: var(--tad-font-family, "Helvetica Neue", Helvetica, Arial, sans-serif); 9 | font-size: var(--tad-font-size, 8pt); 10 | margin: 0; 11 | font-weight: 400; 12 | height: 100%; 13 | overflow: hidden; 14 | } 15 | 16 | 17 | .cell-story { 18 | white-space: normal !important; 19 | line-height: 19px !important; 20 | } 21 | 22 | .loading-spinner { 23 | background: rgba(0, 0, 0, 0.6); 24 | border: none; 25 | } 26 | 27 | .loading-indicator { 28 | display: inline-block; 29 | padding: 12px; 30 | background: white; 31 | -opacity: 0.5; 32 | color: black; 33 | font-weight: bold; 34 | z-index: 9999; 35 | border: 1px solid red; 36 | border-radius: 10px; 37 | -moz-border-radius: 10px; 38 | -webkit-border-radius: 10px; 39 | box-shadow: 0 0 5px red; 40 | -moz-box-shadow: 0 0 5px red; 41 | -webkit-box-shadow: 0px 0px 5px red; 42 | -text-shadow: 1px 1px 1px white; 43 | } 44 | 45 | .loading-indicator label { 46 | padding-left: 20px; 47 | background: url("~slickgrid-es6/images/ajax-loader-small.gif") no-repeat 48 | center left; 49 | } 50 | 51 | .pivot-column { 52 | /* background: white; */ 53 | border: none; 54 | border-right: 1px dotted silver; 55 | } 56 | 57 | .pivot-column.active { 58 | border-color: none; 59 | border-style: none; 60 | border-right: 1px dotted silver; 61 | border-top: 1px dotted silver; 62 | border-bottom: 1px dotted silver; 63 | } 64 | 65 | .data-cell-numeric { 66 | text-align: right; 67 | font-variant-numeric: tabular-nums; 68 | } 69 | 70 | 71 | .bp4-label, 72 | .bp4-control { 73 | font-size: 10pt; 74 | } 75 | 76 | .full-height { 77 | height: 100%; 78 | } 79 | 80 | .no-pad { 81 | padding: 0; 82 | } 83 | 84 | .main-container { 85 | display: flex; 86 | flex-wrap: nowrap; 87 | padding: 0; 88 | } 89 | 90 | .grid-aggregate-row { 91 | font-weight: 600; 92 | } 93 | 94 | .gridPaneOuter { 95 | flex: 1 1 auto; 96 | height: 100%; 97 | overflow: hidden; 98 | } 99 | .gridPaneInner { 100 | width: 100%; 101 | height: 100%; 102 | } 103 | 104 | .columnHeaderCell { 105 | width: 100%; 106 | height: 100%; 107 | } 108 | 109 | .columnHeaderCell rect { 110 | fill: steelblue; 111 | shape-rendering: crispEdges; 112 | } 113 | 114 | #epGrid { 115 | border-left: 1px solid #c0c0c0; 116 | width: 100%; 117 | height: 100%; 118 | } 119 | 120 | .ui-subtext { 121 | font-weight: normal; 122 | font-size: 8pt; 123 | } 124 | 125 | .ui-block { 126 | margin-top: 24px; 127 | margin-bottom: 24px; 128 | } 129 | 130 | table { 131 | border-spacing: 0; 132 | border-collapse: collapse; 133 | } 134 | .table-hover > tbody > tr:hover { 135 | background-color: #f5f5f5; 136 | } 137 | .table > thead > tr > th, 138 | .table > thead > tr > td, 139 | .table > tbody > tr > td { 140 | padding-top: 6px; 141 | padding-bottom: 6px; 142 | padding-left: 8px; 143 | padding-right: 8px; 144 | border-top: 1px solid #ddd; 145 | } 146 | 147 | .center-app-pane { 148 | flex: 1 1 auto; 149 | width: 500px; 150 | display: flex; 151 | flex-direction: column; 152 | flex-wrap: nowrap; 153 | overflow: none; 154 | } 155 | -------------------------------------------------------------------------------- /packages/tadviewer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tadviewer", 3 | "version": "0.14.0", 4 | "description": "Tabular data viewer library", 5 | "main": "dist/tadviewer.js", 6 | "types": "dist/tadviewer.d.ts", 7 | "scripts": { 8 | "prepublish": "npm run build", 9 | "clean": "rimraf dist", 10 | "build:dir": "mkdirp dist", 11 | "build:html": "recursive-copy -w html dist", 12 | "build-assets": "npm run build:dir && npm run build:html", 13 | "build-dev": "npm run build-assets && webpack --mode development", 14 | "build-prod": "npm run build-assets && webpack --env prod --mode production", 15 | "build": "npm run build-prod", 16 | "tsc": "tsc", 17 | "watch": "webpack --mode development --watch", 18 | "watch-prod": "webpack --env prod --mode production --watch" 19 | }, 20 | "keywords": [ 21 | "relational", 22 | "sql", 23 | "database" 24 | ], 25 | "author": "Antony Courtney ", 26 | "license": "MIT", 27 | "dependencies": { 28 | "@blueprintjs/core": "4.12.0", 29 | "@blueprintjs/popover2": "1.10.1", 30 | "@types/he": "^1.1.2", 31 | "he": "^1.2.0", 32 | "immutable": "^4.0.0", 33 | "lodash": "^4.17.21", 34 | "loglevel": "^1.8.0", 35 | "oneref": "^0.9.0", 36 | "react-dnd": "^14.0.5", 37 | "react-dnd-html5-backend": "^14.1.0", 38 | "react-select": "^5.2.1", 39 | "slickgrid-es6": "^3.0.3", 40 | "url-loader": "^4.1.1", 41 | "url-regex": "5.0.0", 42 | "use-deep-compare": "1.1.0", 43 | "victory": "^36.6.10" 44 | }, 45 | "devDependencies": { 46 | "@blueprintjs/icons": "^4.10.1", 47 | "@types/react": "^18.0.15", 48 | "@types/react-dom": "^18.2.4", 49 | "@types/react-select": "^5.0.0", 50 | "@types/slickgrid": "^2.1.31", 51 | "aggtree": "^0.12.0", 52 | "ajv": "^6.9.1", 53 | "css-loader": "^6.5.1", 54 | "file-loader": "^6.2.0", 55 | "image-webpack-loader": "^8.0.1", 56 | "less": "^3.13.1", 57 | "less-loader": "^10.2.0", 58 | "mkdirp": "^1.0.4", 59 | "recursive-copy-cli": "^1.0.20", 60 | "reltab": "^0.12.0", 61 | "resolve-url-loader": "^4.0.0", 62 | "sass": "^1.80.6", 63 | "sass-loader": "^14.2.1", 64 | "source-map-loader": "^3.0.1", 65 | "style-loader": "^3.3.1", 66 | "ts-loader": "^9.2.6", 67 | "webpack": "^5.65.0", 68 | "webpack-cli": "^4.9.1", 69 | "webpack-node-externals": "^3.0.0" 70 | }, 71 | "peerDependencies": { 72 | "aggtree": "^0.12.0", 73 | "react": "^18.2.0", 74 | "react-dom": "^18.2.0", 75 | "reltab": "^0.12.0" 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /packages/tadviewer/src/AppState.ts: -------------------------------------------------------------------------------- 1 | import * as Immutable from "immutable"; 2 | import { ViewState } from "./ViewState"; 3 | import * as reltab from "reltab"; 4 | import { DataSourcePath, DataSourceId } from "reltab"; 5 | import { Timer } from "./Timer"; 6 | import { Activity } from "./components/defs"; 7 | /** 8 | * Immutable representation of application state 9 | * 10 | * Just a single view in a single untabbed window for now. 11 | */ 12 | 13 | export type ExportFormat = "csv" | "parquet"; 14 | 15 | export interface ParquetExportOptions { 16 | compression: "uncompressed" | "snappy" | "gzip" | "zstd"; 17 | } 18 | 19 | export const defaultParquetExportOptions: ParquetExportOptions = { 20 | compression: "snappy", 21 | }; 22 | 23 | export interface AppStateProps { 24 | initialized: boolean; // Has main process initialization completed? 25 | 26 | windowTitle: string; // Usually just the table name or file name 27 | 28 | rtc: reltab.ReltabConnection | null; 29 | 30 | viewState: ViewState | null; 31 | exportBeginDialogOpen: boolean; 32 | exportProgressDialogOpen: boolean; 33 | exportFormat: ExportFormat; 34 | exportPath: string; 35 | exportPathBaseName: string; 36 | exportPercent: number; 37 | 38 | viewConfirmDialogOpen: boolean; 39 | viewConfirmSourcePath: DataSourcePath | null; 40 | 41 | appLoadingTimer: Timer; 42 | activity: Activity; 43 | showRecordCount: boolean; 44 | } 45 | 46 | const defaultAppStateProps: AppStateProps = { 47 | initialized: false, 48 | windowTitle: "", 49 | rtc: null, 50 | viewState: null, 51 | exportBeginDialogOpen: false, 52 | exportProgressDialogOpen: false, 53 | exportFormat: "parquet", 54 | exportPath: "", 55 | exportPathBaseName: "", 56 | exportPercent: 0, 57 | viewConfirmDialogOpen: false, 58 | viewConfirmSourcePath: null, 59 | appLoadingTimer: new Timer(), 60 | activity: "None", 61 | showRecordCount: true, 62 | }; 63 | 64 | export class AppState extends Immutable.Record(defaultAppStateProps) { 65 | public readonly initialized!: boolean; // Has main process initialization completed? 66 | 67 | public readonly windowTitle!: string; // Usually just the table name or file name 68 | 69 | public readonly rtc!: reltab.ReltabConnection; 70 | 71 | public readonly viewState!: ViewState; 72 | public readonly exportBeginDialogOpen!: boolean; 73 | public readonly exportProgressDialogOpen!: boolean; 74 | public readonly exportFormat!: ExportFormat; 75 | public readonly exportPath!: string; 76 | public readonly exportPathBaseName!: string; 77 | public readonly exportPercent!: number; 78 | public readonly viewConfirmDialogOpen!: boolean; 79 | public readonly viewConfirmSourcePath!: DataSourcePath | null; 80 | public readonly appLoadingTimer!: Timer; 81 | public readonly activity!: Activity; 82 | public readonly showRecordCount!: boolean; 83 | } 84 | -------------------------------------------------------------------------------- /packages/tadviewer/src/FormatOptions.ts: -------------------------------------------------------------------------------- 1 | export type CellFormatter = (val?: any) => string | undefined | null; 2 | 3 | export interface ClickHandlerAppContext { 4 | openURL: (url: string) => void; 5 | } 6 | 7 | export type ClickHandler = ( 8 | appContext: ClickHandlerAppContext, 9 | row: number, 10 | column: number, 11 | val: any 12 | ) => void; 13 | 14 | export interface FormatOptions { 15 | getFormatter(): CellFormatter; 16 | getClickHandler(): ClickHandler | null; 17 | getClassName(): string | null; 18 | } 19 | -------------------------------------------------------------------------------- /packages/tadviewer/src/NumFormatOptions.ts: -------------------------------------------------------------------------------- 1 | import * as Immutable from "immutable"; 2 | import { CellFormatter, ClickHandler, FormatOptions } from "./FormatOptions"; 3 | 4 | export interface NumFormatOptionsProps { 5 | type: string; 6 | commas: boolean; 7 | decimalPlaces: number | undefined; 8 | exponential: boolean; 9 | formatMethod: "toLocaleString" | "toString"; 10 | } 11 | 12 | const defaultNumFormatOptionsProps: NumFormatOptionsProps = { 13 | type: "NumFormatOptions", 14 | commas: true, 15 | decimalPlaces: 2, 16 | exponential: false, 17 | formatMethod: "toString", 18 | }; 19 | 20 | export class NumFormatOptions 21 | extends Immutable.Record(defaultNumFormatOptionsProps) 22 | implements NumFormatOptionsProps, FormatOptions 23 | { 24 | public readonly type!: string; 25 | public readonly commas!: boolean; 26 | public readonly decimalPlaces!: number | undefined; 27 | public readonly exponential!: boolean; 28 | 29 | getFormatter(): CellFormatter { 30 | const fmtOpts = { 31 | minimumFractionDigits: this.decimalPlaces, 32 | maximumFractionDigits: this.decimalPlaces, 33 | useGrouping: this.commas, 34 | }; 35 | 36 | const ff = (val?: any): string | undefined | null => { 37 | if (val == null) { 38 | return null; 39 | } 40 | 41 | let ret: string; 42 | 43 | if (this.formatMethod === "toString") { 44 | ret = val.toString(); 45 | } else { 46 | if (this.exponential) { 47 | const expFmtOpts: BigIntToLocaleStringOptions = { 48 | notation: "scientific", 49 | }; 50 | if (this.decimalPlaces) { 51 | expFmtOpts.minimumFractionDigits = this.decimalPlaces as any; 52 | expFmtOpts.maximumFractionDigits = this.decimalPlaces as any; 53 | } 54 | ret = val.toLocaleString(undefined, expFmtOpts); 55 | } else { 56 | ret = val.toLocaleString(undefined, fmtOpts); 57 | } 58 | } 59 | return ret; 60 | }; 61 | 62 | return ff; 63 | } 64 | 65 | getClassName(): string | null { 66 | return "data-cell-numeric"; 67 | } 68 | 69 | getClickHandler(): ClickHandler | null { 70 | return null; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /packages/tadviewer/src/PagedDataView.ts: -------------------------------------------------------------------------------- 1 | import * as reltab from "reltab"; 2 | /* 3 | * a variant on SimpleDataView that maintains a total row count 4 | * and a contiguous subset of rows (a viewport) starting from 5 | * some offset 6 | */ 7 | 8 | // Note: This doesn't explicitly include the '_path' or "_sortVal_X_Y" columns 9 | export interface DataRow { 10 | _isLeaf: boolean; 11 | _depth: number; 12 | _pivot: string; 13 | _isOpen: boolean; 14 | [columnId: string]: reltab.Scalar; 15 | } 16 | 17 | export class PagedDataView { 18 | schema: reltab.Schema; 19 | totalRowCount: number; 20 | offset: number; 21 | rawData: Array; 22 | 23 | constructor( 24 | schema: reltab.Schema, 25 | totalRowCount: number, 26 | offset: number, 27 | items: Array 28 | ) { 29 | this.schema = schema; 30 | this.totalRowCount = totalRowCount; 31 | this.offset = offset; 32 | this.rawData = items; // console.log('PagedDataView: trc: ', totalRowCount, ', offset: ', offset) 33 | } // Unfortunately ambiguous method name comes from SlickGrid 34 | 35 | getLength(): number { 36 | return this.totalRowCount; 37 | } 38 | 39 | getOffset(): number { 40 | return this.offset; 41 | } 42 | 43 | getItemCount(): number { 44 | return this.rawData.length; 45 | } 46 | 47 | getItem(index: number): DataRow | null { 48 | let ret = null; 49 | const itemIndex = index - this.offset; 50 | 51 | if (itemIndex >= 0 && itemIndex < this.rawData.length) { 52 | ret = this.rawData[itemIndex]; 53 | } // console.log('getItem(', index, ') ==> itemIndex: ', itemIndex, ', ret: ', ret) 54 | 55 | return ret; 56 | } 57 | 58 | getItemMetadata(index: number): any { 59 | let ret: any = {}; 60 | const item = this.getItem(index); 61 | 62 | if (item && !item._isLeaf) { 63 | ret.cssClasses = "grid-aggregate-row"; 64 | } 65 | 66 | return ret; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /packages/tadviewer/src/QueryView.ts: -------------------------------------------------------------------------------- 1 | import * as Immutable from "immutable"; 2 | import * as reltab from "reltab"; 3 | /* 4 | * State needed for a scollable view of a reltab query 5 | */ 6 | 7 | export interface QueryViewProps { 8 | baseQuery: reltab.QueryExp | null; 9 | query: reltab.QueryExp | null; 10 | histoMap: reltab.ColumnHistogramMap | null; 11 | rowCount: number; 12 | baseRowCount: number; 13 | filterRowCount: number; 14 | } 15 | 16 | const defaultQueryViewProps: QueryViewProps = { 17 | baseQuery: null, 18 | query: null, 19 | histoMap: null, 20 | rowCount: 0, 21 | // The following fields are just for auxiliary info in footer 22 | baseRowCount: 0, 23 | filterRowCount: 0, 24 | }; 25 | 26 | export class QueryView 27 | extends Immutable.Record(defaultQueryViewProps) 28 | implements QueryViewProps 29 | { 30 | public readonly baseQuery!: reltab.QueryExp; 31 | public readonly query!: reltab.QueryExp; 32 | public readonly histoMap!: reltab.ColumnHistogramMap; 33 | public readonly rowCount!: number; 34 | public readonly baseRowCount!: number; 35 | public readonly filterRowCount!: number; 36 | } 37 | -------------------------------------------------------------------------------- /packages/tadviewer/src/SimpleDataView.ts: -------------------------------------------------------------------------------- 1 | import * as reltab from "reltab"; 2 | 3 | export class SimpleDataView { 4 | rawData: Array; 5 | idMap: Array; 6 | schema: reltab.Schema | undefined | null; 7 | 8 | constructor() { 9 | this.rawData = []; 10 | this.idMap = []; 11 | this.schema = null; 12 | } 13 | 14 | getLength(): number { 15 | return this.rawData.length; 16 | } 17 | 18 | getItem(index: number): any { 19 | return this.rawData[index]; 20 | } 21 | 22 | getItemMetadata(index: number): any { 23 | let ret: any = {}; 24 | const item = this.getItem(index); 25 | 26 | if (!item._isLeaf) { 27 | ret.cssClasses = "grid-aggregate-row"; 28 | } 29 | 30 | return ret; 31 | } 32 | 33 | getItemById(id: number): any { 34 | return this.idMap[id]; 35 | } 36 | 37 | setItems(items: Array): void { 38 | this.rawData = items; 39 | this.idMap = items.slice(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/tadviewer/src/TextFormatOptions.ts: -------------------------------------------------------------------------------- 1 | import * as Immutable from "immutable"; 2 | import * as he from "he"; 3 | import urlRegex from "url-regex"; 4 | import { 5 | CellFormatter, 6 | ClickHandler, 7 | ClickHandlerAppContext, 8 | FormatOptions, 9 | } from "./FormatOptions"; 10 | 11 | /* 12 | * TODO: move to tad-app 13 | const shell = require('electron').shell; // install this globally so we can access in generated a tag: 14 | window.tadOpenExternal = (url: string) => { 15 | console.log('tadOpenExternal: ', url); 16 | shell.openExternal(url); 17 | return false; 18 | }; 19 | */ 20 | 21 | (window as any).tadLinkNOP = (url: string) => { 22 | return false; 23 | }; 24 | 25 | export interface TextFormatOptionsProps { 26 | type: string; 27 | urlsAsHyperlinks: boolean; 28 | } 29 | 30 | const defaultTextFormatOptionsProps: TextFormatOptionsProps = { 31 | type: "TextFormatOptions", 32 | urlsAsHyperlinks: true, 33 | }; 34 | 35 | const isValidURL = (s: string): boolean => 36 | urlRegex({ 37 | exact: true, 38 | }).test(s); 39 | 40 | function stringify(value: any): string | null { 41 | if (value === null) { 42 | return null; 43 | } 44 | switch (typeof value) { 45 | case "string": 46 | return value; 47 | case "object": 48 | const ret = JSON.stringify(value, (_, v) => 49 | typeof v === "bigint" ? v.toString() : v 50 | ); 51 | return ret; 52 | default: 53 | return String(value); 54 | } 55 | } 56 | 57 | export class TextFormatOptions 58 | extends Immutable.Record(defaultTextFormatOptionsProps) 59 | implements TextFormatOptionsProps, FormatOptions 60 | { 61 | public readonly type!: string; 62 | public readonly urlsAsHyperlinks!: boolean; 63 | 64 | getFormatter(): CellFormatter { 65 | const ff = (val?: any): string | undefined | null => { 66 | if (this.urlsAsHyperlinks && val && isValidURL(val)) { 67 | // Just return false from onclick handler to prevent default link handling 68 | const ret = `${val}`; 69 | return ret; 70 | } 71 | // Try to deal with non-string values for cell values like lists or maps 72 | const valStr = stringify(val); 73 | const fmtStr = valStr ? he.encode(valStr) : valStr; 74 | return fmtStr; 75 | }; 76 | 77 | return ff; 78 | } 79 | 80 | getClassName(): string | null { 81 | return null; 82 | } 83 | 84 | getClickHandler(): ClickHandler { 85 | const ch = ( 86 | appContext: ClickHandlerAppContext, 87 | row: number, 88 | column: number, 89 | val: any 90 | ) => { 91 | if (this.urlsAsHyperlinks && val && isValidURL(val)) { 92 | appContext.openURL(val); 93 | } 94 | }; 95 | return ch; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /packages/tadviewer/src/Timer.ts: -------------------------------------------------------------------------------- 1 | import * as Immutable from "immutable"; 2 | /** 3 | * A timer object for use in OneRef-based immutable state 4 | * 5 | */ 6 | 7 | export type TimerUpdater = (f: (ts: Timer) => Timer) => void; // eslint-disable-line 8 | 9 | export interface TimerProps { 10 | running: boolean; 11 | start: number; 12 | elapsed: number; 13 | timerId: number; 14 | } 15 | 16 | const defaultTimerProps: TimerProps = { 17 | running: false, 18 | // true iff timer is running 19 | start: 0, 20 | elapsed: 0, 21 | // in ms 22 | timerId: 0 23 | }; 24 | 25 | export class Timer extends Immutable.Record(defaultTimerProps) 26 | implements TimerProps { 27 | public readonly running!: boolean; 28 | public readonly start!: number; 29 | public readonly elapsed!: number; 30 | public readonly timerId!: number; 31 | /* 32 | * Ensure that the timer is running. 33 | * Will start the timer if not currently running, NOP otherwise 34 | */ 35 | 36 | run(interval: number, updater: TimerUpdater): Timer { 37 | if (this.running) { 38 | return this; 39 | } 40 | 41 | const startTime = new Date().getTime(); 42 | const timerId = window.setInterval(() => this.onTick(updater), interval); 43 | return this.set("running", true) 44 | .set("start", startTime) 45 | .set("elapsed", 0) 46 | .set("timerId", timerId) as Timer; 47 | } 48 | /* 49 | * Stop the timer (by cancelling it) 50 | * A NOP if timer not running 51 | */ 52 | 53 | stop(): Timer { 54 | if (!this.running) { 55 | return this; 56 | } 57 | 58 | window.clearInterval(this.timerId); 59 | return this.remove("running") 60 | .remove("start") 61 | .remove("elapsed") 62 | .remove("timerId") as Timer; 63 | } 64 | 65 | onTick(updater: TimerUpdater) { 66 | updater( 67 | (ts: Timer): Timer => { 68 | if (!ts.running) { 69 | return ts; // possible if tick event was in-flight while cancelled 70 | } 71 | 72 | const curTime = new Date().getTime(); 73 | const elapsed = curTime - ts.start; 74 | return ts.set("elapsed", elapsed) as Timer; 75 | } 76 | ); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /packages/tadviewer/src/ViewState.ts: -------------------------------------------------------------------------------- 1 | import * as Immutable from "immutable"; 2 | import { ViewParams } from "./ViewParams"; 3 | import { QueryView } from "./QueryView"; 4 | import { PagedDataView } from "./PagedDataView"; 5 | import { Timer } from "./Timer"; 6 | import * as reltab from "reltab"; 7 | import { DataSourcePath } from "reltab"; 8 | 9 | /** 10 | * Immutable representation of all state associated 11 | * with a single view. 12 | * 13 | * Consists of user-editable ViewParams plus any associated 14 | * query / network / render state 15 | */ 16 | export interface ViewStateProps { 17 | dbc: reltab.DataSourceConnection | null; 18 | dsPath: DataSourcePath | null; 19 | baseQuery: reltab.QueryExp | null; 20 | baseSchema: reltab.Schema | null; // always in sync with baseQuery 21 | 22 | viewParams: ViewParams; 23 | initialViewParams: ViewParams; // for dirty detection 24 | loadingTimer: Timer; 25 | viewportTop: number; 26 | viewportBottom: number; 27 | queryView: QueryView | undefined | null; 28 | dataView: PagedDataView | undefined | null; 29 | 30 | delayedCalcMode: boolean; // If true, don't recalc from view params until user hits "apply" 31 | } 32 | 33 | const defaultViewStateProps: ViewStateProps = { 34 | dbc: null, 35 | dsPath: null, 36 | baseQuery: null, 37 | baseSchema: null, 38 | viewParams: new ViewParams(), 39 | initialViewParams: new ViewParams(), 40 | loadingTimer: new Timer(), 41 | viewportTop: 0, 42 | viewportBottom: 0, 43 | queryView: null, 44 | dataView: null, 45 | delayedCalcMode: true, 46 | }; 47 | 48 | export class ViewState 49 | extends Immutable.Record(defaultViewStateProps) 50 | implements ViewStateProps 51 | { 52 | public readonly dbc!: reltab.DataSourceConnection; 53 | public readonly dsPath!: DataSourcePath; 54 | public readonly baseQuery!: reltab.QueryExp; 55 | public readonly baseSchema!: reltab.Schema; // always in sync with baseQuery 56 | public readonly viewParams!: ViewParams; 57 | public readonly initialViewParams!: ViewParams; 58 | public readonly loadingTimer!: Timer; 59 | public readonly viewportTop!: number; 60 | public readonly viewportBottom!: number; 61 | public readonly queryView!: QueryView | undefined | null; 62 | public readonly dataView!: PagedDataView | undefined | null; 63 | public readonly delayedCalcMode!: boolean; 64 | } 65 | -------------------------------------------------------------------------------- /packages/tadviewer/src/components/ActivityBar.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Left-most fixed nav bar on application pane 3 | */ 4 | 5 | import * as React from "react"; 6 | import { StateRef } from "oneref"; 7 | import { AppState } from "../AppState"; 8 | import { Button, IconName } from "@blueprintjs/core"; 9 | import { Activity } from "./defs"; 10 | import * as actions from "../actions"; 11 | 12 | export interface ActivityBarProps { 13 | activity: Activity; 14 | showDataSources: boolean; 15 | stateRef: StateRef; 16 | } 17 | 18 | export const ActivityBar: React.FC = ({ 19 | activity, 20 | showDataSources, 21 | stateRef, 22 | }) => { 23 | const handleActivityClick = 24 | (buttonActivity: Activity) => 25 | (e: React.MouseEvent) => { 26 | if (activity === buttonActivity) { 27 | actions.setActivity("None", stateRef); 28 | } else { 29 | actions.setActivity(buttonActivity, stateRef); 30 | } 31 | }; 32 | 33 | const activityButton = ( 34 | target: Activity, 35 | iconName: IconName 36 | ): JSX.Element => ( 37 |