Full Code of sosedoff/pgweb for AI

main ee2e54bfcf28 cached
135 files
1.0 MB
331.7k tokens
681 symbols
1 requests
Download .txt
Showing preview only (1,097K chars total). Download the full file or copy to clipboard to get everything.
Repository: sosedoff/pgweb
Branch: main
Commit: ee2e54bfcf28
Files: 135
Total size: 1.0 MB

Directory structure:
gitextract_uzqtco04/

├── .claude/
│   └── settings.local.json
├── .dockerignore
├── .gitattributes
├── .github/
│   └── workflows/
│       ├── checks.yml
│       ├── deploy.yml
│       ├── docker.yml
│       └── release.yml
├── .gitignore
├── .golangci.yml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── Dockerfile
├── LICENSE
├── Makefile
├── Procfile
├── README.md
├── SCREENS.md
├── config/
│   ├── examples/
│   │   ├── connect_backend_go/
│   │   │   ├── README.md
│   │   │   └── main.go
│   │   └── connect_backend_ruby/
│   │       ├── Gemfile
│   │       ├── README.md
│   │       ├── config.ru
│   │       └── main.rb
│   ├── pgweb.freebsd_rc
│   ├── pgweb.service
│   ├── pgweb_initd.conf
│   └── pgweb_upstart.conf
├── data/
│   ├── bookmark.toml
│   ├── bookmark_invalid_ssl.toml
│   ├── bookmark_url.toml
│   ├── bookmark_with_ssh.toml
│   ├── booktown.sql
│   ├── invalid.toml
│   ├── lc_example1.sql
│   ├── lc_example2.sql
│   ├── lc_invalid_meta.sql
│   ├── lc_no_meta.sql
│   ├── passfile
│   └── roach.sql
├── docker-compose-pg.yml
├── docker-compose.yml
├── fly.toml
├── go.mod
├── go.sum
├── main.go
├── pkg/
│   ├── api/
│   │   ├── api.go
│   │   ├── api_test.go
│   │   ├── errors.go
│   │   ├── helpers.go
│   │   ├── helpers_test.go
│   │   ├── logger.go
│   │   ├── logger_test.go
│   │   ├── middleware.go
│   │   ├── routes.go
│   │   ├── session_manager.go
│   │   ├── session_manager_test.go
│   │   └── types.go
│   ├── bookmarks/
│   │   ├── bookmarks.go
│   │   ├── bookmarks_test.go
│   │   ├── manager.go
│   │   └── manager_test.go
│   ├── cli/
│   │   └── cli.go
│   ├── client/
│   │   ├── client.go
│   │   ├── client_test.go
│   │   ├── codec.go
│   │   ├── codec_test.go
│   │   ├── dump.go
│   │   ├── dump_test.go
│   │   ├── result.go
│   │   ├── result_test.go
│   │   ├── tunnel.go
│   │   ├── util.go
│   │   └── util_test.go
│   ├── command/
│   │   ├── options.go
│   │   ├── options_test.go
│   │   └── version.go
│   ├── connect/
│   │   ├── backend.go
│   │   ├── backend_test.go
│   │   └── types.go
│   ├── connection/
│   │   ├── connection_string.go
│   │   ├── connection_string_test.go
│   │   ├── port.go
│   │   └── port_test.go
│   ├── history/
│   │   └── history.go
│   ├── metrics/
│   │   ├── handler.go
│   │   ├── metrics.go
│   │   └── server.go
│   ├── queries/
│   │   ├── field.go
│   │   ├── field_test.go
│   │   ├── metadata.go
│   │   ├── metadata_test.go
│   │   ├── query.go
│   │   ├── query_test.go
│   │   ├── store.go
│   │   └── store_test.go
│   ├── shared/
│   │   └── ssh_info.go
│   ├── statements/
│   │   ├── sql/
│   │   │   ├── databases.sql
│   │   │   ├── estimated_row_count.sql
│   │   │   ├── function.sql
│   │   │   ├── info.sql
│   │   │   ├── info_simple.sql
│   │   │   ├── materialized_view.sql
│   │   │   ├── objects.sql
│   │   │   ├── schemas.sql
│   │   │   ├── settings.sql
│   │   │   ├── table_constraints.sql
│   │   │   ├── table_indexes.sql
│   │   │   ├── table_info.sql
│   │   │   ├── table_info_cockroach.sql
│   │   │   ├── table_schema.sql
│   │   │   └── tables_stats.sql
│   │   └── sql.go
│   └── util/
│       └── profiler.go
├── script/
│   ├── build_all.sh
│   ├── check_formatting.sh
│   ├── package.sh
│   ├── test_all.sh
│   ├── test_cockroach.sh
│   └── update_homebrew.sh
└── static/
    ├── css/
    │   ├── app.css
    │   ├── bootstrap.css
    │   └── font-awesome.css
    ├── data.go
    ├── fonts/
    │   └── FontAwesome.otf
    ├── index.html
    └── js/
        ├── ace-pgsql.js
        ├── ace.js
        ├── app.js
        ├── base64.js
        ├── bootstrap-contextmenu.js
        ├── bootstrap-dropdown.js
        ├── ext-language_tools.js
        ├── jquery.js
        ├── theme-tomorrow.js
        └── utils.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .claude/settings.local.json
================================================
{
  "permissions": {
    "allow": [
      "Bash(grep:*)",
      "Bash(find:*)",
      "Bash(git checkout:*)",
      "Bash(go test:*)",
      "Bash(go build:*)",
      "Bash(make:*)"
    ]
  }
}


================================================
FILE: .dockerignore
================================================
.github
bin/
./pgweb


================================================
FILE: .gitattributes
================================================
bindata.go -diff


================================================
FILE: .github/workflows/checks.yml
================================================
name: checks

on:
  push:
    branches:
      - main
    paths-ignore:
      - '**.md'
  pull_request:
    types:
      - opened
      - synchronize
    paths-ignore:
      - '**.md'


env:
  GO_VERSION: "1.25"

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  tests:
    name: tests
    runs-on: ubuntu-latest
    timeout-minutes: 40
    strategy:
      matrix:
        pg_version: [9.6, 10, 11, 12, 13, 14, 15, 16, 17, 18]

    services:
      postgres:
        image: postgres:${{ matrix.pg_version }}
        env:
          POSTGRES_USER: postgres
          POSTGRES_PASSWORD: postgres
          POSTGRES_DB: booktown
        ports:
          - 5432:5432
        options: >-
            --health-cmd pg_isready
            --health-interval 10s
            --health-timeout 5s
            --health-retries 5
    steps:
      - name: Install latest Postgres client
        run: |
          sudo rm -f /etc/apt/sources.list.d/pgdg.list
          curl --silent https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add
          echo "deb http://apt.postgresql.org/pub/repos/apt/ `lsb_release -cs`-pgdg main" | sudo tee /etc/apt/sources.list.d/pgdg.list
          sudo apt-get update && sudo apt-get install -y postgresql-client-18

      - uses: actions/checkout@v3
        with:
          fetch-depth: 0
      - uses: actions/setup-go@v3
        with:
          go-version: ${{ env.GO_VERSION }}
      - run: go mod download
      - run: make test
        env:
          MallocNanoZone: 0 # https://github.com/golang/go/issues/49138
          PGHOST: localhost
          PGUSER: postgres
          PGPASSWORD: postgres
          PGDATABASE: booktown

  tests-windows:
    runs-on: windows-latest
    timeout-minutes: 30

    steps:
      - uses: actions/checkout@v3
        with:
          fetch-depth: 0
      - uses: actions/setup-go@v3
        with:
          go-version: ${{ env.GO_VERSION }}
      - run: go mod download
      - run: make test

  lint:
    runs-on: ubuntu-latest
    timeout-minutes: 10

    steps:
      - uses: actions/setup-go@v3
        with:
          go-version: ${{ env.GO_VERSION }}

      - uses: actions/checkout@v3
        with:
          fetch-depth: 0

      - name: golangci-lint
        uses: golangci/golangci-lint-action@v8
        with:
          version: v2.7.2

  fmt:
    name: fmt
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
        with:
          fetch-depth: 0
      - uses: actions/setup-go@v3
        with:
          go-version: ${{ env.GO_VERSION }}
      - run: go mod download
      - run: script/check_formatting.sh


================================================
FILE: .github/workflows/deploy.yml
================================================
name: demo deploy

on:
  push:
    branches:
      - main

env:
  FLY_API_TOKEN: ${{ secrets.FLY_TOKEN }}

jobs:
  deploy:
    name: Deploy to Fly
    runs-on: ubuntu-latest
    timeout-minutes: 10

    steps:
      - uses: actions/checkout@v3
      - uses: superfly/flyctl-actions/setup-flyctl@master
      - run: flyctl deploy --remote-only


================================================
FILE: .github/workflows/docker.yml
================================================
name: docker

on:
  push:
    branches:
      - main

env:
  GO_VERSION: "1.25"
  CGO_ENABLED: 0
  IMAGE_REPOSITORY: sosedoff/pgweb

jobs:
  docker-build:
    name: docker images
    runs-on: ubuntu-latest
    timeout-minutes: 30

    steps:
      - name: Checkout code
        uses: actions/checkout@v3

      - name: Set up QEMU
        uses: docker/setup-qemu-action@v2

      - name: Configure docker build context
        uses: docker/setup-buildx-action@v2

      - name: Build docker images
        uses: docker/build-push-action@v2
        with:
          context: .
          push: false
          tags: pgweb:latest
          platforms: linux/amd64,linux/arm64,linux/arm/v7
          build-args: |
            "CGO_ENABLED=${{ env.CGO_ENABLED }}"


================================================
FILE: .github/workflows/release.yml
================================================
name: release

on:
  push:
    tags:
      - "v*"

env:
  GO_VERSION: "1.25"
  CGO_ENABLED: 0
  DOCKER_REPOSITORY: sosedoff/pgweb
  GHCR_REPOSITORY: sosedoff/pgweb

jobs:
  docker-release:
    name: Publish Docker images
    runs-on: ubuntu-latest
    timeout-minutes: 30

    steps:
      - name: Checkout code
        uses: actions/checkout@v2

      - name: Set up QEMU
        uses: docker/setup-qemu-action@v2

      - name: Configure docker build context
        uses: docker/setup-buildx-action@v2

      - name: Set reference tags
        id: refs
        run: |
          echo ::set-output name=SOURCE_NAME::${GITHUB_REF#refs/*/}
          echo ::set-output name=SOURCE_BRANCH::${GITHUB_REF#refs/heads/}
          echo ::set-output name=SOURCE_TAG::${GITHUB_REF#refs/tags/v}

      - name: Login to Docker Hub
        uses: docker/login-action@v2
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}

      - name: Login to Github Container Registry
        uses: docker/login-action@v2
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GH_TOKEN }}

      - name: Build and push docker images
        uses: docker/build-push-action@v2
        with:
          context: .
          push: true
          tags: |
            ${{ env.DOCKER_REPOSITORY }}:${{ steps.refs.outputs.SOURCE_TAG }}
            ${{ env.DOCKER_REPOSITORY }}:latest
            ghcr.io/${{ env.GHCR_REPOSITORY }}:${{ steps.refs.outputs.SOURCE_TAG }}
            ghcr.io/${{ env.GHCR_REPOSITORY }}:latest
          platforms: linux/amd64,linux/arm64,linux/arm/v7


================================================
FILE: .gitignore
================================================
.DS_Store
.idea
.env
.envrc
pgweb
bin
tmp/
cover.out


================================================
FILE: .golangci.yml
================================================
version: "2"
linters:
  disable:
    - errcheck
  settings:
    staticcheck:
      checks: ["all", "-ST1000", "-ST1003", "-ST1016", "-ST1020", "-ST1021", "-ST1022", "-ST1005", "-QF1004"]


================================================
FILE: CHANGELOG.md
================================================
## Changelog

Current [release](https://github.com/sosedoff/pgweb/releases) is `0.17.0`.

## Next

- `NEW` Add PGWEB_BOOKMARKS_DIR environment variable to configure bookmarks directory

## 0.17.0 - 2025-11-22

- `NEW` Update Dockerfile to use Golang 1.24 image, GH-821
- `NEW` Bump go crypto package to 0.44.x, GH-820, GH-823
- `NEW` Add PostgreSQL 18 support to Github Actions, GH-816
- `NEW` Connect backend refactor, GH-801
- `NEW` Add server settings view, GH-768
- `NEW` Add exec time of empty queries, GH-763
- `FIX` Minor typos, GH-764, GH-786
- `FIX` Shorten git revision printed in the --version output, GH-770

## 0.16.2 - 2024-11-02

- `FIX` Build a new Docker image with PostgreSQL 17 support
- `FIX` Run CI against PostgreSQL 17, GH-758
- `FIX` Rename master to main branch, GH-750

## 0.16.1 - 2024-09-07

- `FIX` Remove linux/arm/v5 from docker release action, GH-742

## 0.16.0 - 2024-06-04

- `NEW` Allow database stats downloads, GH-738
- `NEW` Add analyze table action, GH-737
- `NEW` Bump postgres version used in docker compose to 15, GH-729
- `NEW` Build on Go 1.22, GH-726
- `FIX` SSH tunnel cleanup and parse fixup, GH-731
- `FIX` Drop linux/arm/v5 from docker build, GH-728
- `FIX` Propagate CGO_ENABLED environment variable to docker build, GH-724

## 0.15.0 - 2024-03-14

- `NEW` Add support for a bookmarks-only mode, GH-716
- `FIX` Fix missing indexes by quoting schema/table name to ::regclass, GH-711
- `FIX` Continue on parseJSON error, GH-708

## 0.14.3 - 2024-01-28

- `NEW` Allow retrying a connection on startup, GH-695
- `NEW` Allow setting readonly mode in bookmarks, GH-707
- `FIX` Add UPDATE to list of restricted keywords in read-only mode, GH-697

## 0.14.2 - 2023-10-29

- `NEW` Execute tests using PostgreSQL 16, GH-691
- `FIX` Unclosed database sessions and tunnels, GH-688
- `FIX` Use pg_table_size for table stats query, GH-685
- `FIX` Use `HasSuffix` to correctly determine URL prefix, GH-684

## 0.14.1 - 2023-06-17

- `NEW` Add process start time metric, GH-675
- `NEW` Configure pgweb user for docker container, GH-674
- `NEW` Updated dockerfile, GH-645
- `FIX` Fix typo in the healthy metric, GH-657
- `NEW` Use entrypoint instead of cmd in in dockerfile, GH-654

## 0.14.0 - 2023-02-21

- `FIX` History page query loading fixup, GH-632
- `NEW` Display cell content via context menu, GH-634
- `NEW` Handle support/permissions errors in info call, GH-635
- `NEW` Show error message when API calls fail, GH-636
- `NEW` Add bookmark options to load username/password from env vars, GH-638
- `NEW` Add context menu to display database tables stats, GH-639
- `NEW` Added Local Queries feature, GH-641
- `FIX` Ensure that objects are sorted by schema and name, GH-648
- `FIX` Fetch local queries on db connect, GH-650

## 0.13.1 - 2022-12-27

- Fix connect flow when `~/.pgweb/bookmarks` directory is not available, GH-631

## 0.13.0 - 2022-12-25

- Add support for .pgpass file, GH-617
- Request logging additions (request id, forwarded user), GH-618
- Establish connections using bookmark ID only, GH-619
- Display empty schemas on the sidebar, GH-621
- Configure timeout and retries when testing connection status, GH-623
- Setup basic prom metrics endpoint, GH-624
- Add default connect_timeout option to connection string, GH-626
- Add duration_ms to log entries, GH-628
- Add query execution stats to api endpoint, GH-629

## 0.12.0 - 2022-12-13

- Deprecate usage of Gox for binary builds, GH-571
- Add netcat install in dockerfile to provide a way to healthcheck, GH-572
- Install latest postgres client in docker image, GH-577
- Add support for `PGWEB_` prefix environment variables, GH-585
- Fix export URL generation, refactor export code, GH-588
- Add logrus-based request logger, GH-589
- Configure logger for connect backend, GH-591
- Set LDFLAGS for make build/release commands, GH-592
- Add internal sessions manager, GH-593
- Include index size on the index list view, GH-595
- Fix flaky backend connection test, GH-596
- Add ability to view and copy views/materialized views definitions, GH-594
- Enable dev assets mode with PGWEB_ASSETS_DEVMODE env var, GH-597
- Make query input box resizable, GH-599
- Deprecate Heroku demo deployments and switch to Fly, GH-600
- Handle returning values in update/delete queries, GH-601
- Fix panic with invalid time marshaling, GH-602
- Configure logging level and format, GH-605
- Use go embed to load queries from static files, GH-607
- Switch go build target to 1.19, GH-603
- Add support for user functions, GH-608
- Implement global query timeout option, GH-609
- Switch windows tests from Appveyor to Github Actions, GH-611
- Fix activity endpoint panic when server version is not detected, GH-612

## 0.11.12 - 2022-07-05

- Update base docker image (alpine), update deps, GH-558
- Refactor docker images building, include ARM, GH-568

## 0.11.11 - 2022-03-29

- Auto-detect the query from the query source based on user selection, GH-547
- Added binary codec base58 as well as improving the help for --binary-codec flag, GH-548
- Change binary codec back to none, GH-555

## 0.11.10 - 2022-01-20

- Removes alert on column copy value, GH-536
- Migrate test suite to Github Action, GH-540
- Serialize binary bytea cols into hex/base64, GH-537
- Include build time into version string, GH-541
- Explain analyze dropdown button, GH-532
- Switch to go 1.17, GH-543
- Use HTTP 302 status code for successful backend redirect, GH-544
- Add connect backend tests, GH-546

## 0.11.9 - 2021-11-08

- Releases are built on Go 1.17
- Build time correction, GH-521
- Fix broken assets URL path prefix, GH-525
- Update docker build image to alpine:3.14, GH-522
- Upgrade gin dependency to v1.7.4, GH-527
- Add FreeBSD startup script, GH-520

## 0.11.8 - 2021-07-07

- Releases are built with Go 1.16
- Add ARM64 v7 build target, GH-497
- Switch to Go modules for dependency management, GH-509
- Switch to Go embed for static assets management, GH-510
- Add Darwin/ARM64 build target (Apple Silicon), GH-513

## 0.11.7 - 2020-10-18

- Releases are built with Go 1.15
- Show results row context menu on custom query results, GH-457
- Do not terminate if local authentication failed on start, GH-463
- Do not show other databases if session is locked, GH-470
- Strip debug information from binary to reduce size, GH-489
- Disable autocomplete on database search field, GH-492
- Improve windows connection error matching during start, GH-493

## 0.11.6 - 2020-02-19

- Add CLI options for SSL key, cert and root certs, GH-452
- Remove double click action on cell, GH-455

## 0.11.5 - 2019-12-16

- Add basic SQL keyword autocompletion, GH-443
- SSH Private Key handling update (encrypted keys are supported now), GH-445
- Include Go version into `pgweb --version` output, GH-447
- Fix long table name bug in the sidebar, GH-448
- Add SQL objects (table,views,etc) autocompletion, GH-449
- Include Go version into info API endpoint, GH-450

## 0.11.4 - 2019-10-05

- Fix SQL export filename, GH-438
- Update Docker image to alpine:3.10, GH-439
- Drop unsupported pg_dump options from connection string, GH-441
- Misc code cleanup and formatting, GH-442

## 0.11.3 - 2019-07-24

- Misc: add script to update homebrew formula version, GH-423
- Destructive keyword restriction in read-only mode, GH-421
- Make database object searchable in sidebar, GH-434
- Update lib/pg to 1.1.1, GH-435

## 0.11.2 - 2019-02-15

- Fix table row estimation query for camelcase schemas, GH-414

## 0.11.1 - 2019-01-28

- Typo fixes
- Add Base64 javascript encoder/decoder to replace deprecated window.atob call, GH-405
- Fix startup error when DATABASE_URL is set, GH-406
- Fix user auto detection when USER env var is not set, GH-408
- Switch bindata dependency to use maintained fork: github.com/go-bindata/go-bindata, GH-409

## 0.11.0 - 2018-12-24

- Tweak sidebar database object counters styles, GH-400
- Do not exit with error if local server is not running, GH-399
- Fix SSH host verification check, GH-398
- Scope activity list to current database only, GH-397
- Show current release version and check for updates, GH-396
- Force switch back to default connection settings view, GH-395
- Fix row count estimation bug, GH-394
- Print out failed query SQL and args with --debug flag, GH-393

## 0.10.0 - 2018-11-28

- Fixes relation not found errors when dealing with table names that have uppercase characters, GH-356
- Dockerfile updates, GH-357
- Check if pg_dump is available before running database export, GH-358
- Improvements to CockroachDB integration, GH-365
- Add EstimatedTableRowsCount to avoid count in large tables, GH-366
- Automatically set table filter option to 'equals' if its not set, GH-370
- Dependencies update and switch to dep, GH-375
- Add column context menu item to get numeric stats, GH-377
- Fix issues with connection string builder, GH-378
- Include rows count to numeric stats view on table column, GH-379
- Make localhost to be a default db host, GH-380
- Clear out connection settings/bookmark on login screen when running in session/connect mode
- Add table row context menu with actions, GH-381
- Allow settings url prefix with URL_PREFIX env var, GH-387
- Fix JSON marshal panic when dealing with NaN values, GH-388
- Fix startup behavior when user did not provide a database name, GH-389

## 0.9.12 - 2018-04-23

- Add link to view database connection string format on login page
- Include constraint name under "constraints" tab, GH-343
- Misc CI and config changes

## 0.9.11 - 2017-12-07

- Fix ssl mode for the connection url in the bookmarks, GH-320
- Add support for CORS, GH-321
- Fix custom query results counter for empty queries, GH-322
- Reorganize the table context menu, GH-323
- Disable database connection string text field autocomplete, GH-327
- Add db prefix to the table export files, GH-329
- Add database view context menu with export actions, GH-330

## 0.9.10 - 2017-11-03

- Make idle connection timeout configurable, [GH-282]
- Fix panics when sshinfo is not set on bookmarks, [GH-296]
- Dot now allow using startup bookmark in multi-session mode, [GH-300]
- Add ability to copy table name from the sidebar, [GH-301]

## 0.9.9 - 2017-09-28

- Automatically format JSON data exports, GH-255
- Update Docker image to alpine:3.6, GH-256
- Print out PostgreSQL server version on start in a single-session mode, GH-264
- Record last query timestamp for the client connection, GH-265
- Add context menu for table headers in browse mode (copy name, see unique values), GH-268
- Add ability to export current database dump, GH-270
- Automatically open pgweb in browser on start if its already running, GH-272
- Connect to the database with credentials provided by a third-party backend, GH-266
- Automatically close idle sessions (no activity in 1 hour), GH-275
- Allow connecting via SSH with a custom private key and other fixes, GH-277
- Add options to disable SSH connections, GH-279

## 0.9.8 - 2017-08-04

- Fixed error checking in the API, GH-234
- Fixed activity tab to support PG 9.x versions, GH-237
- Remember sort column and order for pagination, GH-240
- Use `sslmode=disable` for bookmarks without sslmode option, GH-244
- Javascript fixes for IE9-11, GH-245
- Require confirmation for the disconnect, GH-246
- Clean the results table on manual disconnect

## 0.9.7 - 2017-04-04

- Fixed issue with locked session and empty db url, GH-206
- Fixed path rewrite on DB change, GH-212
- Upgraded dependencies, GH-217
- Added ability to specify bookmarks path, GH-218
- Added counter for the number of rows from a custom SQL query, GH-224
- Added new behavior for removing table rows view on custom SQL query page, GH-225

## 0.9.6 - 2016-11-18

- Fixed bug in query base64-encoding, GH-186
- Fixed rows pagination visibility bug, GH-190
- Fixed issue with query order escaping, GH-191
- Fixed invalid query selection for explain command, GH-198
- Fixed issue with empty sidebar, now it shows empty state, GH-202
- Added new flag --readonly to enable read only transaction mode, GH-193
- Added ability to kill any running query, GH-194
- Added session database connection locking, GH-195
- Added ability to switch between databases, GH-196
- Added feature to keep last selected tab when switching between tables, GH-197
- Added new flag --bookmark (-b) to specify server connection from bookmark, GH-201

## 0.9.5 - 2016-10-01

- Only view schema with USAGE privileges, GH-167
- Fixed broken export to CSV/JSON/XML if hashmark in URL, GH-175
- Added example service configuration for systemd, GH-177
- Allow setting auth user and pass using variables

## 0.9.4 - 2016-07-29

- Fixes CSV/JSON/XML export buttons when pgweb is running with url prefix, GH-170

## 0.9.3 - 2016-06-30

- Uses Go 1.6 for development, GH-155
- Fixes timestamp formatting in CSV export, GH-163
- Included PostgreSQL 9.6 for integration testing
- Switches docker image to Alpine to reduce image size
- Adds support for ARMv5

## 0.9.2 - 2016-03-01

- Fixes bug with unsafe base64 encoded sql queries
- Fixes issue with session id not being included in multi-session mode
- Fixes visual issue with long table names in sidebar
- Fixes visual issue with a scrollbar in table information widget
- Fixes issue with database connection form being reset by clicking on 'cancel' button
- Adds ability to close connection
- Adds display message for number of affected rows for update/delete queries, GH-133
- Adds web server url prefix as a CLI option, GH-135

## 0.9.1 - 2016-01-25

- Fixes bug with tables context menu
- Fixes JS bug when query returns no rows
- Fixes bug with switching between different connection modes
- Adds AJAX timeout to 5s
- Adds sidebar reload action on any CREATE/DROP action

## 0.9.0 - 2016-01-19

- Add support for multiple schemas. GH-112
- Add support for native ssh tunnes. GH-114
- Add materialized views to list of schema objects
- Adds a few design tweaks and cleanups
- Fixes bug with nil result set when fetching rows

## 0.8.0 - 2016-01-11

- Fixes bug with bigint conversions in javascript. Now bigints are encoded as strings. GH-109
- Adds pagination and simple column filtering to table rows browser. GH-110
- Adds ability to use pgweb with multiple database sessions. GH-111
- Adds a few design tweaks and cleanups

## 0.7.0 - 2016-01-05

- Adds sequences to the sidebar panel - GH-100
- Adds table constrains view - GH-104
- Adds ability to export table and query rows as JSON/XML - GH-107
- Updates to UI theme and SQL editor

## 0.6.3 - 2015-08-16

- Adds PostgreSQL password escaping in web ui, GH-96
- Adds base64 query encoding for CSV export, GH-95
- Adds automatic saving of last executed query to localStorage
- Adds request middleware to log incoming form params in debug mode

## 0.6.2 - 2015-07-15

- Adds ability to specify connection strings prefixed by `postgresql://`, [GH-92]
- Updates configuration for Heroku, [GH-89], [GH-90]
- Updates postgresql library dependency to latest, [GH-91]
- Fixes password field to not display plaintext passwords, [GH-87]

## 0.6.1 - 2015-06-18

- This release is repackage-release targeted to fix binary downloads

## 0.6.0 - 2015-05-31

- Adds ability to execute only selected SQL query in run command view, [GH-85]
- Adds ability to delete/truncate table via context many on sidebar view
- Adds ability to export table contents to CSV via context menu on sidebar view
- Changes sidebar color scheme to a lighter and better looking one

## 0.5.3 - 2015-05-06

- Changes default server port from 8080 to 8081 to avoil conflict with RethinkDB
- Changes styles for table rows and connection settings window
- Adds highlighting styles for columns with sort order
- Adds git sha into program version output
- Add new endpoint /api/info to get build details

## 0.5.2 - 2015-04-13

- Adds a new endpoint /activity that returns active queries
- Adds tab to view active queries
- Adds column sorting when browsing table contents
- Fixes SQL query view when switching to table structure view

## 0.5.1 - 2015-02-23

- Upgrades Gin framework dependency to 0.5.0
- Fixes server crash if another pgweb server is running

## 0.5.0 - 2015-01-13

- Adds Go 1.4 support
- Adds connection string printing in debug mode
- Adds initial bookmarks support
- Adds /api prefix for all API calls
- Adds makefile usage task
- Adds windows CI to verify build process
- Adds example sql database to codebase
- Adds timestamped filenames when exporting results to CSV [GH-75]
- Adds connection checking on each request to prevent api panics
- Adds timestamps to query history records
- Adds current database name to the sidebar
- Adds button to refresh tables list to the sidebar
- Updates all application dependencies
- Changes /api/info endpoint to /api/connection
- Fixes issues with connection string/options parsing
- Fixes capitalized column names in table view
- Fixes connection string validation in /api/connect endpoint

## 0.4.1 - 2014-12-01

- Adds pgweb version on start [GH-65]
- Adds user detection from OS environment
- Adds simple memory profiles with --debug option
- Adds the session user and search path in connection info [GH-67]
- Adds table list reloading after CREATE/DROP TABLE queries [GH-69]
- Adds font awesome icons for the sidebar
- Removes query recording for internal queries [GH-67]
- Fixes default sslmode. Its not longer set to "disable"
- Fixes cells cropping on table indexes view
- Fixes connection URL generation using web interface
- Fixes SQL statements for table row count [GH-67]
- Fixes /tables JSON response if database does not have any tables

## 0.4.0 - 2014-11-11

- Adds query escaping when exporting results to CSV [GH-38]
- Adds keyboard shortcut (ctrl+e, command+e on mac) for query explain action
- Adds HTTP basic authentication with --auth-user and --auth-pass flags
- Adds -skip-open/-s flag to disable automatic browser launch
- Adds --bind option to specify server listen hostname/ip
- Adds ssl mode parameters to url if ssl flag is set and not defined in the url
- Adds dependency management with Godep
- Adds Docker support
- Adds Heroku support
- Adds ability to connect to databases with no tables
- Adds precompiled assets into repository to simplify development
- Adds a connection details view
- Adds a new interface to specify connection settings or make a new connection
- Adds page favicon
- Adds ability to present cell data as text area by double clicking on it
- Fixes styles for query explain results
- Fixes sidebar navigation scrolling styles [GH-12]
- Fixes sidebar table name styles to support long names

## 0.3.1 - 2014-10-28

- Adds proper exit code when printing version via -v/--version flag
- Adds --version and --debug long flag names
- Adds double quotes for table name when fetching table contents
- Adds support for DATABASE_URL environment variable if no --url is set
- Adds proper usage of jQuery .prop method
- Adds --pass flag to specify connection password
- Fixes --ssl flag usage, previous value was hardcoded

## 0.3.0 - 2014-10-26

- Renamed `make deps` to `make setup` and fix issues with bootstrapping
- Removed hardcoded url for CSV export, it now detects application host:port
- Improved query history view table styles
- Moved table information view to the sidebar
- Added --listen flag to specify web server port, still defaults to 8080

## 0.2.0 - 2014-10-23

- Design tweaks
- Automatically opens browser on OSX systems
- Adds query explain functionality
- Adds export to CSV

## 0.1.0 - 2014-10-14

- Initial release


================================================
FILE: CONTRIBUTING.md
================================================
- Fork repository
- Create a new git branch
- Make changes
- Run tests: `make test`
- Run tests against all supported PostreSQL versions: `make test-all` (optional)
- If you change frontend code (js/css) make sure to rebuild assets: `make assets`
- Open a new pull request

================================================
FILE: Dockerfile
================================================
# ------------------------------------------------------------------------------
# Builder Stage
# ------------------------------------------------------------------------------
FROM golang:1.25-trixie AS build

# Set default build argument for CGO_ENABLED
ARG CGO_ENABLED=0
ENV CGO_ENABLED=${CGO_ENABLED}

WORKDIR /build

RUN git config --global --add safe.directory /build
COPY go.mod go.sum ./
RUN go mod download
COPY Makefile main.go ./
COPY static/ static/
COPY pkg/ pkg/
COPY .git/ .
RUN make build

# ------------------------------------------------------------------------------
# Fetch signing key
# ------------------------------------------------------------------------------
FROM debian:trixie-slim AS keyring
ADD https://www.postgresql.org/media/keys/ACCC4CF8.asc keyring.asc
RUN apt-get update && \
    apt-get install -qq --no-install-recommends gpg
RUN gpg -o keyring.pgp --dearmor keyring.asc

# ------------------------------------------------------------------------------
# Release Stage
# ------------------------------------------------------------------------------
FROM debian:trixie-slim

ARG keyring=/usr/share/keyrings/postgresql-archive-keyring.pgp
COPY --from=keyring /keyring.pgp $keyring
RUN . /etc/os-release && \
    echo "deb [signed-by=${keyring}] http://apt.postgresql.org/pub/repos/apt/ ${VERSION_CODENAME}-pgdg main" > /etc/apt/sources.list.d/pgdg.list && \
    apt-get update && \
    apt-get install -qq --no-install-recommends ca-certificates openssl netcat-openbsd curl postgresql-client

COPY --from=build /build/pgweb /usr/bin/pgweb

RUN useradd --uid 1000 --no-create-home --shell /bin/false pgweb
USER pgweb

EXPOSE 8081
ENTRYPOINT ["/usr/bin/pgweb", "--bind=0.0.0.0", "--listen=8081"]


================================================
FILE: LICENSE
================================================
The MIT License (MIT)

Copyright (c) 2014-2024 Dan Sosedoff <dan.sosedoff@gmail.com>

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

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

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


================================================
FILE: Makefile
================================================
PKG = github.com/sosedoff/pgweb
GIT_COMMIT ?= $(shell git rev-parse --short=8 HEAD)
BUILD_TIME ?= $(shell date -u +"%Y-%m-%dT%H:%M:%SZ" | tr -d '\n')
GO_VERSION ?= $(shell go version | awk {'print $$3'})

DOCKER_RELEASE_TAG = "sosedoff/pgweb:$(shell git describe --abbrev=0 --tags | sed 's/v//')"
DOCKER_LATEST_TAG = "sosedoff/pgweb:latest"

LDFLAGS = -s -w
LDFLAGS += -X $(PKG)/pkg/command.GitCommit=$(GIT_COMMIT)
LDFLAGS += -X $(PKG)/pkg/command.BuildTime=$(BUILD_TIME)
LDFLAGS += -X $(PKG)/pkg/command.GoVersion=$(GO_VERSION)

usage:
	@echo ""
	@echo "Task                 : Description"
	@echo "-----------------    : -------------------"
	@echo "make dev             : Generate development build"
	@echo "make build           : Generate production build for current OS"
	@echo "make release         : Generate binaries for all supported OSes"
	@echo "make test            : Execute test suite"
	@echo "make test-all        : Execute test suite on multiple PG versions"
	@echo "make lint            : Execute code linter"
	@echo "make clean           : Remove all build files and reset assets"
	@echo "make docker          : Build docker image"
	@echo "make docker-release  : Build and tag docker image"
	@echo "make docker-push     : Push docker images to registry"
	@echo ""

test:
	go test -v -race -cover ./pkg/...

test-all:
	@./script/test_all.sh
	@./script/test_cockroach.sh

lint:
	golangci-lint run

dev:
	go build
	@echo "You can now execute ./pgweb"

build:
	go build -ldflags '${LDFLAGS}'
	@echo "You can now execute ./pgweb"

install:
	go install -ldflags '${LDFLAGS}'
	@echo "You can now execute pgweb"

release: clean
	@echo "Building binaries..."
	@LDFLAGS='${LDFLAGS}' ./script/build_all.sh

clean:
	@echo "Removing all artifacts"
	@rm -rf ./pgweb ./bin/*

docker:
	docker build --no-cache -t pgweb .

docker-run:
	docker run --rm -p 8081:8081 -it pgweb

docker-release:
	docker build --no-cache -t $(DOCKER_RELEASE_TAG) .
	docker tag $(DOCKER_RELEASE_TAG) $(DOCKER_LATEST_TAG)
	docker images $(DOCKER_RELEASE_TAG)

docker-push:
	docker push $(DOCKER_RELEASE_TAG)
	docker push $(DOCKER_LATEST_TAG)


================================================
FILE: Procfile
================================================
web: pgweb --url=$DATABASE_URL --listen=$PORT --bind=0.0.0.0 --auth-user=$AUTH_USER --auth-pass=$AUTH_PASS


================================================
FILE: README.md
================================================
# pgweb

Simple web-based and cross platform PostgreSQL database explorer.

[![Release](https://img.shields.io/github/release/sosedoff/pgweb.svg?label=Release)](https://github.com/sosedoff/pgweb/releases)
[![Linux Build](https://github.com/sosedoff/pgweb/actions/workflows/checks.yml/badge.svg)](https://github.com/sosedoff/pgweb/actions?query=branch%3Amain)
[![Go Report Card](https://goreportcard.com/badge/github.com/sosedoff/pgweb)](https://goreportcard.com/report/github.com/sosedoff/pgweb)
[![GoDoc](https://godoc.org/github.com/sosedoff/pgweb?status.svg)](https://godoc.org/github.com/sosedoff/pgweb)
[![Docker Pulls](https://img.shields.io/docker/pulls/sosedoff/pgweb.svg)](https://hub.docker.com/r/sosedoff/pgweb/)

## Overview

Pgweb is a web-based database explorer for PostgreSQL, written in Go, and works
on Mac, Linux and Windows machines. Distributed as a simple binary with zero dependencies.
Very easy to use and packs just the right amount of features.

[See application screenshots](SCREENS.md)

## Features

- Cross-platform: Mac/Linux/Windows (64bit).
- Simple installation (distributed as a single binary).
- Zero dependencies.
- Works with PostgreSQL 9.1+.
- Supports native SSH tunnels.
- Multiple database sessions.
- Execute and analyze custom SQL queries.
- Table and query data export to CSV/JSON/XML.
- Query history.
- Server bookmarks.

Visit [WIKI](https://github.com/sosedoff/pgweb/wiki) for more details.

## Demo

Visit https://pgweb-demo.fly.dev/ to see Pgweb in action.

## Installation

- [Precompiled binaries](https://github.com/sosedoff/pgweb/releases) for supported operating systems are available.
- [More installation options](https://github.com/sosedoff/pgweb/wiki/Installation)

## Usage

Start server:

```
pgweb
```

You can also provide connection flags:

```
pgweb --host localhost --user myuser --db mydb
```

Connection URL scheme is also supported:

```
pgweb --url postgres://user:password@host:port/database?sslmode=[mode]
pgweb --url "postgres:///database?host=/absolute/path/to/unix/socket/dir"
```

### Multiple database sessions

To enable multiple database sessions in pgweb, start the server with:

```
pgweb --sessions
```

Or set environment variable:

```
PGWEB_SESSIONS=1 pgweb
```

## Testing

Before running tests, make sure you have PostgreSQL server running on `localhost:5432`
interface. Also, you must have `postgres` user that could create new databases
in your local environment. Pgweb server should not be running at the same time.

Execute test suite:

```
make test
```

If you're using Docker locally, you might also run pgweb test suite against
all supported PostgreSQL version with a single command:

```
make test-all
```

## Contribute

- Fork this repository
- Create a new feature branch for a new functionality or bugfix
- Commit your changes
- Execute test suite
- Push your code and open a new pull request
- Use [issues](https://github.com/sosedoff/pgweb/issues) for any questions
- Check [wiki](https://github.com/sosedoff/pgweb/wiki) for extra documentation

## License

The MIT License (MIT). See [LICENSE](LICENSE) file for more details.


================================================
FILE: SCREENS.md
================================================
# Screenshots

### Connect
<img src="screenshots/connect.png" />

### Browse table rows
<img src="screenshots/browse.png" />

### Write SQL queries
<img src="screenshots/query.png" />

================================================
FILE: config/examples/connect_backend_go/README.md
================================================
# connect-backend-go

Example Golang backend for Pgweb Connect feature

## Usage

Run the backend:

```bash
go run main.go
```

Configure pgweb:

```bash
pgweb --sessions --connect-backend=http://localhost:4567 --connect-token=test
```


================================================
FILE: config/examples/connect_backend_go/main.go
================================================
package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
)

type BackendRequest struct {
	Resource string            `json:"resource"`
	Token    string            `json:"token"`
	Headers  map[string]string `json:"headers"`
}

type BackendResponse struct {
	DatabaseURL string `json:"database_url"`
}

func main() {
	resources := map[string]string{
		"id1": "postgres://localhost:5432/db1?sslmode=disable",
		"id2": "postgres://localhost:5432/db2?sslmode=disable",
		"id3": "postgres://localhost:5432/db3?sslmode=disable",
	}

	http.HandleFunc("/", func(rw http.ResponseWriter, req *http.Request) {
		backendReq := BackendRequest{}

		if err := json.NewDecoder(req.Body).Decode(&backendReq); err != nil {
			rw.WriteHeader(400)
			fmt.Fprintf(rw, "error while parsing request: %v", err)
			return
		}

		res, ok := resources[backendReq.Resource]
		if !ok {
			rw.WriteHeader(404)
			return
		}

		resp := BackendResponse{
			DatabaseURL: res,
		}

		json.NewEncoder(rw).Encode(resp)
	})

	if err := http.ListenAndServe(":4567", nil); err != nil {
		log.Fatal(err)
	}
}


================================================
FILE: config/examples/connect_backend_ruby/Gemfile
================================================
source "https://rubygems.org"

gem "sinatra"
gem "json"
gem "puma"
gem "rackup"


================================================
FILE: config/examples/connect_backend_ruby/README.md
================================================
# connect-backend-ruby

Example Ruby backend for Pgweb Connect feature

## Usage

Install and run the backend:

```bash
bundle install
ruby main.rb
```

Configure pgweb:

```bash
pgweb --sessions --connect-backend=http://localhost:4567 --connect-token=test
```


================================================
FILE: config/examples/connect_backend_ruby/config.ru
================================================
require "bundler/setup"
require "./main"

run Sinatra::Application


================================================
FILE: config/examples/connect_backend_ruby/main.rb
================================================
require "bundler/setup"
require "sinatra"

# Authentication token
$token = "test"

# List of all available resources
$resources = {
  "id1" => "postgres://localhost:5432/db1?sslmode=disable",
  "id2" => "postgres://localhost:5432/db2?sslmode=disable",
  "id3" => "postgres://localhost:5432/db3?sslmode=disable"
}

helpers do
  def error(code, message)
    halt(code, JSON.dump(error: message))
  end
end

before do
  content_type :json
end

post "/" do
  req = JSON.load(request.body) || {}

  unless req["resource"]
    halt 404, "Resource ID required"
  end

  # Check the resource
  resource = $resources[req["resource"]]
  if !resource
    halt 404, "Invalid resource ID"
  end

  # Return connection credentials
  JSON.dump(
    database_url: resource
  )
end


================================================
FILE: config/pgweb.freebsd_rc
================================================
#!/bin/sh
#
# $FreeBSD: $
#
# PROVIDE: pgweb
# REQUIRE: NETWORKING
# KEYWORD:
#
# Add the following lines to /etc/rc.conf to enable pgweb:
# pgweb_enable="YES"
#
# pgweb_enable (bool):              Set to YES to enable pgweb
#                                       Default: NO
# pgweb_bind (str):               HTTP server host
#                                       Default: localhost
# pgweb_listen (str):   HTTP server listen port
#                                       Default: 8081
# pgweb_user (str):         pgweb daemon user
#                                       Default: www
# pgweb_group (str):                pgweb daemon group
#                                       Default: www

. /etc/rc.subr

name="pgweb"
rcvar="pgweb_enable"
load_rc_config $name

: ${pgweb_user:="www"}
: ${pgweb_group:="www"}
: ${pgweb_enable:="NO"}
: ${pgweb_bind:="localhost"}
: ${pgweb_flags=""}
: ${pgweb_facility:="daemon"}
: ${pgweb_priority:="debug"}
: ${pgweb_listen:="8081"}

procname="/usr/local/bin/${name}"
pidfile="/var/run/${name}.pid"
start_precmd="${name}_precmd"
command=/usr/sbin/daemon
command_args="-S -l ${pgweb_facility} -s ${pgweb_priority} -T ${name} -t ${name} -p ${pidfile} \
        ${procname} --bind=${pgweb_bind} --listen=${pgweb_listen} ${pgweb_flags}"

pgweb_precmd()
{
        install -o ${pgweb_user} /dev/null ${pidfile}
}

run_rc_command "$1"


================================================
FILE: config/pgweb.service
================================================
[Unit]
Description=pgweb - Cross-platform client for PostgreSQL databases
After=network.target

[Service]
Type=simple
ExecStart=/usr/bin/pgweb --bind=0.0.0.0 --listen=8081
Restart=on-abort

[Install]
WantedBy=multi-user.target


================================================
FILE: config/pgweb_initd.conf
================================================
#!/bin/sh
### BEGIN INIT INFO
# Provides:          pgweb
# Required-Start:    $remote_fs $syslog
# Required-Stop:     $remote_fs $syslog
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Start daemon at boot time
# Description:       Enable service provided by daemon.
### END INIT INFO

# Installation instructions (originally written for Debian)
#
# Save this script into /etc/init.d/pgweb file, make it executable,
# and install it into the boot sequence:
#
#    chmod 755 /etc/init.d/pgweb
#    update-rc.d pgweb defaults
# 
# This script assumes that pgweb binary is located at /home/pgweb/, and that
# there's a bookmark 'server' in /home/pgweb/.pgweb/bookmarks/.
#

NAME="pgweb"
PIDFILE="/var/run/$NAME.pid"

USER="pgweb"  # Linux system user
SU="su $USER -s /bin/bash"

TIMEOUT=5  # Time in seconds to wait postgresql to show up

case "$1" in
  start)
    if [ -f $PIDFILE ]; then
        echo "Already running... cat $PIDFILE"
        exit 0
    fi
    
    # Wait postgresql to show up
    while ! test -f /var/run/postgresql/*main.pid
    do
        sleep 1
        TIMEOUT=`expr $TIMEOUT - 1`
        if test $TIMEOUT -eq 0; then
            exit 1
        fi
    done
    
    # Ready to start pgweb
    PID=`$SU -c '/home/pgweb/pgweb -s -b server >/dev/null & echo $!'`  # Note! Logs are lost.
    if [ -z $PID ]; then
        exit 1
    else
        echo $PID > $PIDFILE
    fi
    ;;
  stop)
    PID=`cat $PIDFILE`
    kill $PID && rm $PIDFILE
    ;;
  *)
    echo "Usage: /etc/init.d/$NAME {start|stop}"
    exit 1
    ;;
esac

exit 0

================================================
FILE: config/pgweb_upstart.conf
================================================
description "PgWeb as a Service for Ubuntu 14.04 With Upstart"

start on runlevel [2345]
stop on runlevel [!2345]

respawn
setuid youruser
setgid www-data

exec /usr/bin/pgweb --bind=0.0.0.0 --listen=8081


================================================
FILE: data/bookmark.toml
================================================
host = "localhost"
port = 5432
user = "postgres"
database = "mydatabase"
ssl = "disable"


================================================
FILE: data/bookmark_invalid_ssl.toml
================================================
host = "localhost"
port = 5432
user = "postgres"
database = "mydatabase"
ssl = "disabled"


================================================
FILE: data/bookmark_url.toml
================================================
url = "postgres://username:password@host:port/database?sslmode=disable"


================================================
FILE: data/bookmark_with_ssh.toml
================================================
host = "localhost"
port = 5432
user = "postgres"
database = "mydatabase"
ssl = "disable"

[SSH]
host = "ssh-host"
user = "ssh-user"
password = "ssh-password"
key = "/path/to/key-file"
keypassword = "key-file-password"


================================================
FILE: data/booktown.sql
================================================
--
-- Selected TOC Entries:
--
--
-- TOC Entry ID 1 (OID 0)
--
-- Name: booktown Type: DATABASE Owner: postgres
--

DROP DATABASE IF EXISTS "booktown";
CREATE DATABASE "booktown";

-- \connect booktown postgres
--
-- TOC Entry ID 2 (OID 2991542)
--
-- Name: DATABASE "booktown" Type: COMMENT Owner: 
--

CREATE TABLE "dummies" (
  "id" integer NOT NULL,
  "isDummy" boolean
);

INSERT INTO "dummies" VALUES (1, true);
INSERT INTO "dummies" VALUES (2, true);


COMMENT ON DATABASE "booktown" IS 'The Book Town Database.';

--
-- TOC Entry ID 33 (OID 3629264)
--
-- Name: books Type: TABLE Owner: manager
--

CREATE TABLE "books" (
	"id" integer NOT NULL,
	"title" text NOT NULL,
	"author_id" integer,
	"subject_id" integer,
	Constraint "books_id_pkey" Primary Key ("id")
);

--
-- TOC Entry ID 47 (OID 2991733)
--
-- Name: "plpgsql_call_handler" () Type: FUNCTION Owner: postgres
--

CREATE FUNCTION "plpgsql_call_handler" () RETURNS opaque AS '/usr/local/pgsql/lib/plpgsql.so', 'plpgsql_call_handler' LANGUAGE 'C';

--
-- TOC Entry ID 48 (OID 2991734)
--
-- Name: plpgsql Type: PROCEDURAL LANGUAGE Owner: 
--

CREATE TRUSTED PROCEDURAL LANGUAGE 'plpgsql' HANDLER "plpgsql_call_handler" LANCOMPILER 'PL/pgSQL';

--
-- TOC Entry ID 51 (OID 2991735)
--
-- Name: "audit_bk" (integer) Type: FUNCTION Owner: postgres
--

CREATE FUNCTION "audit_bk" (integer) RETURNS integer AS '
	DECLARE
	 key ALIAS FOR $1;
	table_data inventory%ROWTYPE;
  BEGIN
	INSERT INTO inventory_audit SELECT table_data WHERE sort_key=key;
	
	IF NOT FOUND THEN
	  RAISE EXCEPTION ''View'' || key || '' not found '';
	END IF;
	
	return 1;
end;
' LANGUAGE 'plpgsql';

--
-- TOC Entry ID 52 (OID 2991736)
--
-- Name: "audit" (integer) Type: FUNCTION Owner: postgres
--

CREATE FUNCTION "audit" (integer) RETURNS integer AS '
	DECLARE
	 key ALIAS FOR $1;
	table_data inventory%ROWTYPE;
  BEGIN
	INSERT INTO inventory_audit SELECT table_data WHERE sort_key=key;
	
	IF NOT FOUND THEN
	  RAISE EXCEPTION ''View'' || key || '' not found '';
	END IF;
	
	return 1;
end;
' LANGUAGE 'plpgsql';

--
-- TOC Entry ID 53 (OID 2991737)
--
-- Name: "auditbk" () Type: FUNCTION Owner: postgres
--

CREATE FUNCTION "auditbk" () RETURNS integer AS '
	DECLARE
	 key ALIAS FOR $1;
	table_data inventory%ROWTYPE;
  BEGIN
	INSERT INTO inventory_audit SELECT table_data WHERE sort_key=key;
	
	IF NOT FOUND THEN
	  RAISE EXCEPTION ''View'' || key || '' not found '';
	END IF;
	
	return 1;
end;
' LANGUAGE 'plpgsql';

--
-- TOC Entry ID 54 (OID 2991738)
--
-- Name: "audit_bk1" () Type: FUNCTION Owner: postgres
--

CREATE FUNCTION "audit_bk1" () RETURNS opaque AS '
	DECLARE
	 key ALIAS FOR $1;
	table_data inventory%ROWTYPE;
  BEGIN
	INSERT INTO inventory_audit SELECT table_data WHERE sort_key=key;
	
	IF NOT FOUND THEN
	  RAISE EXCEPTION ''View'' || key || '' not found '';
	END IF;
	
	return 1;
end;
' LANGUAGE 'plpgsql';

--
-- TOC Entry ID 73 (OID 2991835)
--
-- Name: "test_check_a_id" () Type: FUNCTION Owner: example
--

CREATE FUNCTION "test_check_a_id" () RETURNS opaque AS '
    BEGIN
     -- checks to make sure the author id
     -- inserted is not left blank or less than 100

        IF NEW.a_id ISNULL THEN
           RAISE EXCEPTION
           ''The author id cannot be left blank!'';
        ELSE
           IF NEW.a_id < 100 THEN
              RAISE EXCEPTION
              ''Please insert a valid author id.'';
           ELSE
           RETURN NEW;
           END IF;
        END IF;
    END;
' LANGUAGE 'plpgsql';

--
-- TOC Entry ID 66 (OID 2992619)
--
-- Name: "audit_test" () Type: FUNCTION Owner: example
--

CREATE FUNCTION "audit_test" () RETURNS opaque AS '
    BEGIN   
       
      IF TG_OP = ''INSERT'' OR TG_OP = ''UPDATE'' THEN

         NEW.user_aud := current_user;
         NEW.mod_time := ''NOW'';

        INSERT INTO inventory_audit SELECT * FROM inventory WHERE prod_id=NEW.prod_id;
              
      RETURN NEW; 

      ELSE if TG_OP = ''DELETE'' THEN
        INSERT INTO inventory_audit SELECT *, current_user, ''NOW'' FROM inventory WHERE prod_id=OLD.prod_id;

      RETURN OLD;
      END IF;
     END IF;
    END;
' LANGUAGE 'plpgsql';

--
-- TOC Entry ID 67 (OID 3000878)
--
-- Name: "first" () Type: FUNCTION Owner: example
--

CREATE FUNCTION "first" () RETURNS integer AS ' 
       DecLarE
        oNe IntEgER := 1;
       bEGiN
        ReTUrn oNE;       
       eNd;
' LANGUAGE 'plpgsql';

--
-- TOC Entry ID 68 (OID 3000881)
--
-- Name: "test" (integer) Type: FUNCTION Owner: example
--

CREATE FUNCTION "test" (integer) RETURNS integer AS '
  
 DECLARE 
   -- defines the variable as ALIAS
  variable ALIAS FOR $1;
 BEGIN
  -- displays the variable after multiplying it by two 
  return variable * 2.0;
 END; 
 ' LANGUAGE 'plpgsql';

--
-- TOC Entry ID 69 (OID 3000991)
--
-- Name: "you_me" (integer) Type: FUNCTION Owner: example
--

CREATE FUNCTION "you_me" (integer) RETURNS integer AS '
  DECLARE
   RENAME $1 TO user_no;
    --you INTEGER := 5;
  BEGIN
    return user_no;
  END;' LANGUAGE 'plpgsql';

--
-- TOC Entry ID 62 (OID 3001136)
--
-- Name: "count_by_two" (integer) Type: FUNCTION Owner: example
--

CREATE FUNCTION "count_by_two" (integer) RETURNS integer AS '
     DECLARE
          userNum ALIAS FOR $1;
          i integer;
     BEGIN
          i := 1;
          WHILE userNum[1] < 20 LOOP
                i = i+1; 
                return userNum;              
          END LOOP;
          
     END;
   ' LANGUAGE 'plpgsql';

--
-- TOC Entry ID 63 (OID 3001139)
--
-- Name: "me" () Type: FUNCTION Owner: example
--

CREATE FUNCTION "me" () RETURNS text AS '
  DECLARE
     you text := ''testing'';
     RENAME you to me;
  BEGIN
     return me;
  END;' LANGUAGE 'plpgsql';

--
-- TOC Entry ID 64 (OID 3001149)
--
-- Name: "display_cust" (integer) Type: FUNCTION Owner: example
--

CREATE FUNCTION "display_cust" (integer) RETURNS text AS '
 DECLARE
   -- declares an alias name for input
   cust_num ALIAS FOR $1;

   -- declares a row type
   cust_info customer%ROWTYPE;
 BEGIN
   -- puts information into the newly declared rowtype
   SELECT into cust_info * 
     FROM customer 
    WHERE cust_id=cust_num;    

   -- displays the customer lastname
   -- extracted from the rowtype   
   return cust_info.lastname;
 END;
 ' LANGUAGE 'plpgsql';

--
-- TOC Entry ID 65 (OID 3001151)
--
-- Name: "mixed" () Type: FUNCTION Owner: example
--

CREATE FUNCTION "mixed" () RETURNS integer AS '
       DecLarE
          --assigns 1 to the oNe variable
          oNe IntEgER 
          := 1;

       bEGiN

          --displays the value of oNe
          ReTUrn oNe;       
       eNd;
       ' LANGUAGE 'plpgsql';

--
-- TOC Entry ID 12 (OID 3117548)
--
-- Name: publishers Type: TABLE Owner: postgres
--

CREATE TABLE "publishers" (
	"id" integer NOT NULL,
	"name" text,
	"address" text,
	Constraint "publishers_pkey" Primary Key ("id")
);

--
-- TOC Entry ID 55 (OID 3117729)
--
-- Name: "compound_word" (text,text) Type: FUNCTION Owner: example
--

CREATE FUNCTION "compound_word" (text,text) RETURNS text AS '
     DECLARE
       -- defines an alias name for the two input values
       word1 ALIAS FOR $1;
       word2 ALIAS FOR $2;
     BEGIN
       -- displays the resulting joined words
       RETURN word1 || word2;
     END;
  ' LANGUAGE 'plpgsql';

--
-- TOC Entry ID 56 (OID 3117787)
--
-- Name: "givename" () Type: FUNCTION Owner: example
--

CREATE FUNCTION "givename" () RETURNS opaque AS '
 DECLARE
   tablename text;
 BEGIN
   
   tablename = TG_RELNAME; 
   INSERT INTO INVENTORY values (123, tablename);
   return old;
 END;
' LANGUAGE 'plpgsql';

--
-- TOC Entry ID 14 (OID 3389594)
--
-- Name: authors Type: TABLE Owner: manager
--

CREATE TABLE "authors" (
	"id" integer NOT NULL,
	"last_name" text,
	"first_name" text,
	Constraint "authors_pkey" Primary Key ("id")
);

--
-- TOC Entry ID 15 (OID 3389632)
--
-- Name: states Type: TABLE Owner: postgres
--

CREATE TABLE "states" (
	"id" integer NOT NULL,
	"name" text,
	"abbreviation" character(2),
	Constraint "state_pkey" Primary Key ("id")
);

--
-- TOC Entry ID 16 (OID 3389702)
--
-- Name: my_list Type: TABLE Owner: postgres
--

CREATE TABLE "my_list" (
	"todos" text
);

--
-- TOC Entry ID 17 (OID 3390348)
--
-- Name: stock Type: TABLE Owner: postgres
--

CREATE TABLE "stock" (
	"isbn" text NOT NULL,
	"cost" numeric(5,2),
	"retail" numeric(5,2),
	"stock" integer,
	Constraint "stock_pkey" Primary Key ("isbn")
);

--
-- TOC Entry ID 4 (OID 3390416)
--
-- Name: subject_ids Type: SEQUENCE Owner: postgres
--

CREATE SEQUENCE "subject_ids" start 0 increment 1 maxvalue 2147483647 minvalue 0  cache 1 ;

--
-- TOC Entry ID 19 (OID 3390653)
--
-- Name: numeric_values Type: TABLE Owner: postgres
--

CREATE TABLE "numeric_values" (
	"num" numeric(30,6)
);

--
-- TOC Entry ID 20 (OID 3390866)
--
-- Name: daily_inventory Type: TABLE Owner: postgres
--

CREATE TABLE "daily_inventory" (
	"isbn" text,
	"is_stocked" boolean
);

--
-- TOC Entry ID 21 (OID 3391084)
--
-- Name: money_example Type: TABLE Owner: postgres
--

CREATE TABLE "money_example" (
	"money_cash" money,
	"numeric_cash" numeric(6,2)
);

--
-- TOC Entry ID 22 (OID 3391184)
--
-- Name: shipments Type: TABLE Owner: postgres
--

CREATE TABLE "shipments" (
	"id" integer DEFAULT nextval('"shipments_ship_id_seq"'::text) NOT NULL,
	"customer_id" integer,
	"isbn" text,
	"ship_date" timestamp with time zone
);

--
-- TOC Entry ID 24 (OID 3391454)
--
-- Name: customers Type: TABLE Owner: manager
--

CREATE TABLE "customers" (
	"id" integer NOT NULL,
	"last_name" text,
	"first_name" text,
	Constraint "customers_pkey" Primary Key ("id")
);

--
-- TOC Entry ID 6 (OID 3574018)
--
-- Name: book_ids Type: SEQUENCE Owner: postgres
--

CREATE SEQUENCE "book_ids" start 0 increment 1 maxvalue 2147483647 minvalue 0  cache 1 ;

--
-- TOC Entry ID 26 (OID 3574043)
--
-- Name: book_queue Type: TABLE Owner: postgres
--

CREATE TABLE "book_queue" (
	"title" text NOT NULL,
	"author_id" integer,
	"subject_id" integer,
	"approved" boolean
);

--
-- TOC Entry ID 78 (OID 3574403)
--
-- Name: "title" (integer) Type: FUNCTION Owner: postgres
--

CREATE FUNCTION "title" (integer) RETURNS text AS 'SELECT title from books where id = $1' LANGUAGE 'sql';

--
-- TOC Entry ID 27 (OID 3574983)
--
-- Name: stock_backup Type: TABLE Owner: postgres
--

CREATE TABLE "stock_backup" (
	"isbn" text,
	"cost" numeric(5,2),
	"retail" numeric(5,2),
	"stock" integer
);

--
-- TOC Entry ID 89 (OID 3625934)
--
-- Name: "double_price" (double precision) Type: FUNCTION Owner: postgres
--

CREATE FUNCTION "double_price" (double precision) RETURNS double precision AS '
  DECLARE
  BEGIN
    return $1 * 2;
  END;
' LANGUAGE 'plpgsql';

--
-- TOC Entry ID 90 (OID 3625935)
--
-- Name: "triple_price" (double precision) Type: FUNCTION Owner: postgres
--

CREATE FUNCTION "triple_price" (double precision) RETURNS double precision AS '
  DECLARE
     -- Declare input_price as an alias for the
     -- argument variable normally referenced with
     -- the $1 identifier.
    input_price ALIAS FOR $1;
 
  BEGIN
     -- Return the input price multiplied by three.
    RETURN input_price * 3;
  END;
 ' LANGUAGE 'plpgsql';

--
-- TOC Entry ID 87 (OID 3625944)
--
-- Name: "stock_amount" (integer,integer) Type: FUNCTION Owner: postgres
--

CREATE FUNCTION "stock_amount" (integer,integer) RETURNS integer AS '
  DECLARE
     -- Declare aliases for function arguments.
    b_id ALIAS FOR $1;
    b_edition ALIAS FOR $2;
     -- Declare variable to store the ISBN number.
    b_isbn TEXT;
     -- Declare variable to store the stock amount.
    stock_amount INTEGER;
  BEGIN
     -- This SELECT INTO statement retrieves the ISBN
     -- number of the row in the editions table that had
     -- both the book ID number and edition number that
     -- were provided as function arguments.
    SELECT INTO b_isbn isbn FROM editions WHERE
      book_id = b_id AND edition = b_edition;
 
     -- Check to see if the ISBN number retrieved
     -- is NULL.  This will happen if there is not an
     -- existing book with both the ID number and edition
     -- number specified in the function arguments.
     -- If the ISBN is null, the function returns a
     -- value of -1 and ends.
    IF b_isbn IS NULL THEN
      RETURN -1;
    END IF;
 
     -- Retrieve the amount of books available from the
     -- stock table and record the number in the
     -- stock_amount variable.
    SELECT INTO stock_amount stock FROM stock WHERE isbn = b_isbn;
 
     -- Return the amount of books available.
    RETURN stock_amount;
  END;
' LANGUAGE 'plpgsql';

--
-- TOC Entry ID 86 (OID 3625946)
--
-- Name: "in_stock" (integer,integer) Type: FUNCTION Owner: postgres
--

CREATE FUNCTION "in_stock" (integer,integer) RETURNS boolean AS '
  DECLARE
    b_id ALIAS FOR $1;
    b_edition ALIAS FOR $2;
    b_isbn TEXT;
    stock_amount INTEGER;
  BEGIN
     -- This SELECT INTO statement retrieves the ISBN
     -- number of the row in the editions table that had
     -- both the book ID number and edition number that
     -- were provided as function arguments.
    SELECT INTO b_isbn isbn FROM editions WHERE
      book_id = b_id AND edition = b_edition;
 
     -- Check to see if the ISBN number retrieved
     -- is NULL.  This will happen if there is not an
     -- existing book with both the ID number and edition
     -- number specified in the function arguments.
     -- If the ISBN is null, the function returns a
     -- FALSE value and ends.
    IF b_isbn IS NULL THEN
      RETURN FALSE;
    END IF;
 
     -- Retrieve the amount of books available from the
     -- stock table and record the number in the
     -- stock_amount variable.
    SELECT INTO stock_amount stock FROM stock WHERE isbn = b_isbn;
 
     -- Use an IF/THEN/ELSE check to see if the amount
     -- of books available is less than, or equal to 0.
     -- If so, return FALSE.  If not, return TRUE.
    IF stock_amount <= 0 THEN
      RETURN FALSE;
    ELSE
      RETURN TRUE;
    END IF;
  END;
' LANGUAGE 'plpgsql';

--
-- TOC Entry ID 82 (OID 3626013)
--
-- Name: "extract_all_titles" () Type: FUNCTION Owner: postgres
--

CREATE FUNCTION "extract_all_titles" () RETURNS text AS '
  DECLARE
    sub_id INTEGER;
    text_output TEXT = '' '';
    sub_title TEXT;
    row_data books%ROWTYPE;
  BEGIN
    FOR i IN 0..15 LOOP
      SELECT INTO sub_title subject FROM subjects WHERE id = i;
      text_output = text_output || ''
'' || sub_title || '':
'';

      FOR row_data IN SELECT * FROM books
        WHERE subject_id = i  LOOP

        IF NOT FOUND THEN
          text_output := text_output || ''None.
'';
        ELSE
          text_output := text_output || row_data.title || ''
'';
        END IF;

      END LOOP;
    END LOOP;
    RETURN text_output;
  END;
' LANGUAGE 'plpgsql';

--
-- TOC Entry ID 79 (OID 3626052)
--
-- Name: "books_by_subject" (text) Type: FUNCTION Owner: postgres
--

CREATE FUNCTION "books_by_subject" (text) RETURNS text AS '
  DECLARE
    sub_title ALIAS FOR $1;
    sub_id INTEGER;
    found_text TEXT :='''';
  BEGIN
      SELECT INTO sub_id id FROM subjects WHERE subject = sub_title;
      RAISE NOTICE ''sub_id = %'',sub_id;
      IF sub_title = ''all'' THEN
        found_text := extract_all_titles();
        RETURN found_text;
      ELSE IF sub_id  >= 0 THEN
          found_text := extract_title(sub_id);
          RETURN  ''
'' || sub_title || '':
'' || found_text;
        END IF;
    END IF;
    RETURN ''Subject not found.'';
  END;
' LANGUAGE 'plpgsql';

--
-- TOC Entry ID 81 (OID 3626590)
--
-- Name: "add_two_loop" (integer,integer) Type: FUNCTION Owner: postgres
--

CREATE FUNCTION "add_two_loop" (integer,integer) RETURNS integer AS '
  DECLARE
 
     -- Declare aliases for function arguments.
 
    low_number ALIAS FOR $1;
    high_number ALIAS FOR $2;
 
     -- Declare a variable to hold the result.
 
    result INTEGER = 0;
 
  BEGIN
 
    WHILE result != high_number LOOP
      result := result + 1;
    END LOOP;
 
    RETURN result;
  END;
' LANGUAGE 'plpgsql';

--
-- TOC Entry ID 92 (OID 3627916)
--
-- Name: "extract_all_titles2" () Type: FUNCTION Owner: postgres
--

CREATE FUNCTION "extract_all_titles2" () RETURNS text AS '
  DECLARE
    sub_id INTEGER;
    text_output TEXT = '' '';
    sub_title TEXT;
    row_data books%ROWTYPE;
  BEGIN
    FOR i IN 0..15 LOOP
      SELECT INTO sub_title subject FROM subjects WHERE id = i;
      text_output = text_output || ''
'' || sub_title || '':
'';

      FOR row_data IN SELECT * FROM books
        WHERE subject_id = i  LOOP

        text_output := text_output || row_data.title || ''
'';

      END LOOP;
    END LOOP;
    RETURN text_output;
  END;
' LANGUAGE 'plpgsql';

--
-- TOC Entry ID 94 (OID 3627974)
--
-- Name: "extract_title" (integer) Type: FUNCTION Owner: postgres
--

CREATE FUNCTION "extract_title" (integer) RETURNS text AS '
  DECLARE
    sub_id ALIAS FOR $1;
    text_output TEXT :=''
'';
    row_data RECORD;
  BEGIN
    FOR row_data IN SELECT * FROM books
    WHERE subject_id = sub_id ORDER BY title  LOOP
      text_output := text_output || row_data.title || ''
'';
    END LOOP;
    RETURN text_output;
  END;
' LANGUAGE 'plpgsql';

--
-- TOC Entry ID 95 (OID 3628021)
--
-- Name: "raise_test" () Type: FUNCTION Owner: postgres
--

CREATE FUNCTION "raise_test" () RETURNS integer AS '
  DECLARE
 
     -- Declare an integer variable for testing.
 
    an_integer INTEGER = 1;
 
  BEGIN
 
     -- Raise a debug level message.
 
    RAISE DEBUG ''The raise_test() function began.'';
 
    an_integer = an_integer + 1;
 
     -- Raise a notice stating that the an_integer
     -- variable was changed, then raise another notice
     -- stating its new value.
 
    RAISE NOTICE ''Variable an_integer was changed.'';
    RAISE NOTICE ''Variable an_integer value is now %.'',an_integer;
 
     -- Raise an exception.
 
    RAISE EXCEPTION ''Variable % changed.  Aborting transaction.'',an_integer;
 
  END;
' LANGUAGE 'plpgsql';

--
-- TOC Entry ID 93 (OID 3628069)
--
-- Name: "add_shipment" (integer,text) Type: FUNCTION Owner: postgres
--

CREATE FUNCTION "add_shipment" (integer,text) RETURNS timestamp with time zone AS '
  DECLARE
    customer_id ALIAS FOR $1;
    isbn ALIAS FOR $2;
    shipment_id INTEGER;
    right_now timestamp;
  BEGIN
    right_now := ''now'';
    SELECT INTO shipment_id id FROM shipments ORDER BY id DESC;
    shipment_id := shipment_id + 1;
    INSERT INTO shipments VALUES ( shipment_id, customer_id, isbn, right_now );
    RETURN right_now;
  END;
' LANGUAGE 'plpgsql';

--
-- TOC Entry ID 102 (OID 3628076)
--
-- Name: "ship_item" (text,text,text) Type: FUNCTION Owner: postgres
--

CREATE FUNCTION "ship_item" (text,text,text) RETURNS integer AS '
  DECLARE
    l_name ALIAS FOR $1;
    f_name ALIAS FOR $2;
    book_isbn ALIAS FOR $3;
    book_id INTEGER;
    customer_id INTEGER;
 
  BEGIN
 
    SELECT INTO customer_id get_customer_id(l_name,f_name);
 
    IF customer_id = -1 THEN
      RETURN -1;
    END IF;
 
    SELECT INTO book_id book_id FROM editions WHERE isbn = book_isbn;
 
    IF NOT FOUND THEN
      RETURN -1;
    END IF;
 
    PERFORM add_shipment(customer_id,book_isbn);
 
    RETURN 1;
  END;
' LANGUAGE 'plpgsql';

--
-- TOC Entry ID 103 (OID 3628114)
--
-- Name: "check_book_addition" () Type: FUNCTION Owner: postgres
--

CREATE FUNCTION "check_book_addition" () RETURNS opaque AS '
  DECLARE 
    id_number INTEGER;
    book_isbn TEXT;
  BEGIN

    SELECT INTO id_number id FROM customers WHERE id = NEW.customer_id; 

    IF NOT FOUND THEN
      RAISE EXCEPTION ''Invalid customer ID number.'';  
    END IF;

    SELECT INTO book_isbn isbn FROM editions WHERE isbn = NEW.isbn; 

    IF NOT FOUND THEN
      RAISE EXCEPTION ''Invalid ISBN.''; 
    END IF; 

    UPDATE stock SET stock = stock -1 WHERE isbn = NEW.isbn; 

    RETURN NEW; 
  END;
' LANGUAGE 'plpgsql';

--
-- TOC Entry ID 28 (OID 3628246)
--
-- Name: stock_view Type: VIEW Owner: postgres
--

CREATE VIEW "stock_view" as SELECT stock.isbn, stock.retail, stock.stock FROM stock;

CREATE MATERIALIZED VIEW "m_stock_view" as SELECT stock.isbn, stock.retail, stock.stock FROM stock;

--
-- TOC Entry ID 30 (OID 3628247)
--
-- Name: favorite_books Type: TABLE Owner: manager
--

CREATE TABLE "favorite_books" (
	"employee_id" integer,
	"books" text[]
);

--
-- TOC Entry ID 8 (OID 3628626)
--
-- Name: shipments_ship_id_seq Type: SEQUENCE Owner: manager
--

CREATE SEQUENCE "shipments_ship_id_seq" start 0 increment 1 maxvalue 2147483647 minvalue 0  cache 1 ;

--
-- TOC Entry ID 74 (OID 3628648)
--
-- Name: "check_shipment_addition" () Type: FUNCTION Owner: postgres
--

CREATE FUNCTION "check_shipment_addition" () RETURNS opaque AS '
  DECLARE
     -- Declare a variable to hold the customer ID.
    id_number INTEGER;
 
     -- Declare a variable to hold the ISBN.
    book_isbn TEXT;
  BEGIN
 
     -- If there is an ID number that matches the customer ID in
     -- the new table, retrieve it from the customers table.
    SELECT INTO id_number id FROM customers WHERE id = NEW.customer_id;
 
     -- If there was no matching ID number, raise an exception.
    IF NOT FOUND THEN
      RAISE EXCEPTION ''Invalid customer ID number.'';
    END IF;
 
     -- If there is an ISBN that matches the ISBN specified in the
     -- new table, retrieve it from the editions table.
    SELECT INTO book_isbn isbn FROM editions WHERE isbn = NEW.isbn;
 
     -- If there is no matching ISBN, raise an exception.
    IF NOT FOUND THEN
      RAISE EXCEPTION ''Invalid ISBN.'';
    END IF;
 
    -- If the previous checks succeeded, update the stock amount
    -- for INSERT commands.
    IF TG_OP = ''INSERT'' THEN
       UPDATE stock SET stock = stock -1 WHERE isbn = NEW.isbn;
    END IF;
 
    RETURN NEW;
  END;
' LANGUAGE 'plpgsql';

--
-- TOC Entry ID 31 (OID 3628899)
--
-- Name: employees Type: TABLE Owner: postgres
--

CREATE TABLE "employees" (
	"id" integer NOT NULL,
	"last_name" text NOT NULL,
	"first_name" text,
	CONSTRAINT "employees_id" CHECK ((id > 100)),
	Constraint "employees_pkey" Primary Key ("id")
);

--
-- TOC Entry ID 32 (OID 3629174)
--
-- Name: editions Type: TABLE Owner: manager
--

CREATE TABLE "editions" (
	"isbn" text NOT NULL,
	"book_id" integer,
	"edition" integer,
	"publisher_id" integer,
	"publication" date,
	"type" character(1),
	CONSTRAINT "integrity" CHECK (((book_id NOTNULL) AND (edition NOTNULL))),
	Constraint "pkey" Primary Key ("isbn")
);

--
-- TOC Entry ID 10 (OID 3629402)
--
-- Name: author_ids Type: SEQUENCE Owner: manager
--

CREATE SEQUENCE "author_ids" start 0 increment 1 maxvalue 2147483647 minvalue 0  cache 1 ;

--
-- TOC Entry ID 35 (OID 3629424)
--
-- Name: distinguished_authors Type: TABLE Owner: manager
--

CREATE TABLE "distinguished_authors" (
	"award" text
)
INHERITS ("authors");

--
-- TOC Entry ID 107 (OID 3726476)
--
-- Name: "isbn_to_title" (text) Type: FUNCTION Owner: manager
--

CREATE FUNCTION "isbn_to_title" (text) RETURNS text AS 'SELECT title FROM books
                                 JOIN editions AS e (isbn, id)
                                 USING (id)
                                 WHERE isbn = $1' LANGUAGE 'sql';

--
-- TOC Entry ID 36 (OID 3727889)
--
-- Name: favorite_authors Type: TABLE Owner: manager
--

CREATE TABLE "favorite_authors" (
	"employee_id" integer,
	"authors_and_titles" text[]
);

--
-- TOC Entry ID 99 (OID 3728728)
--
-- Name: "get_customer_name" (integer) Type: FUNCTION Owner: postgres
--

CREATE FUNCTION "get_customer_name" (integer) RETURNS text AS '
  DECLARE
  
    -- Declare aliases for user input.
    customer_id ALIAS FOR $1;
    
    -- Declare variables to hold the customer name.
    customer_fname TEXT;
    customer_lname TEXT;
  
  BEGIN
  
    -- Retrieve the customer first and last name for the customer whose
    -- ID matches the value supplied as a function argument.
    SELECT INTO customer_fname, customer_lname 
                first_name, last_name FROM customers
      WHERE id = customer_id;
    
    -- Return the name.
    RETURN customer_fname || '' '' || customer_lname;
  END;
' LANGUAGE 'plpgsql';

--
-- TOC Entry ID 100 (OID 3728729)
--
-- Name: "get_customer_id" (text,text) Type: FUNCTION Owner: postgres
--

CREATE FUNCTION "get_customer_id" (text,text) RETURNS integer AS '
  DECLARE
 
    -- Declare aliases for user input.
    l_name ALIAS FOR $1;
    f_name ALIAS FOR $2;
 
    -- Declare a variable to hold the customer ID number.
    customer_id INTEGER;
 
  BEGIN
 
    -- Retrieve the customer ID number of the customer whose first and last
    --  name match the values supplied as function arguments.
    SELECT INTO customer_id id FROM customers
      WHERE last_name = l_name AND first_name = f_name;
 
    -- Return the ID number.
    RETURN customer_id;
  END;
' LANGUAGE 'plpgsql';

--
-- TOC Entry ID 101 (OID 3728730)
--
-- Name: "get_author" (text) Type: FUNCTION Owner: postgres
--

CREATE FUNCTION "get_author" (text) RETURNS text AS '
  DECLARE
 
      -- Declare an alias for the function argument,
      -- which should be the first name of an author.
     f_name ALIAS FOR $1;
 
       -- Declare a variable with the same type as
       -- the last_name field of the authors table.
     l_name authors.last_name%TYPE;
 
  BEGIN
 
      -- Retrieve the last name of an author from the
      -- authors table whose first name matches the
      -- argument received by the function, and
      -- insert it into the l_name variable.
     SELECT INTO l_name last_name FROM authors WHERE first_name = f_name;
 
       -- Return the first name and last name, separated
       -- by a space.
     return f_name || '' '' || l_name;
 
  END;
' LANGUAGE 'plpgsql';

--
-- TOC Entry ID 97 (OID 3728759)
--
-- Name: "get_author" (integer) Type: FUNCTION Owner: postgres
--

CREATE FUNCTION "get_author" (integer) RETURNS text AS '
  DECLARE
 
    -- Declare an alias for the function argument,
    -- which should be the id of the author.
    author_id ALIAS FOR $1;
 
    -- Declare a variable that uses the structure of
    -- the authors table.
    found_author authors%ROWTYPE;
 
  BEGIN
 
    -- Retrieve a row of author information for
    -- the author whose id number matches
    -- the argument received by the function.
    SELECT INTO found_author * FROM authors WHERE id = author_id;
 
    -- Return the first
    RETURN found_author.first_name || '' '' || found_author.last_name;
 
  END;
' LANGUAGE 'plpgsql';

--
-- TOC Entry ID 70 (OID 3743412)
--
-- Name: "html_linebreaks" (text) Type: FUNCTION Owner: postgres
--

CREATE FUNCTION "html_linebreaks" (text) RETURNS text AS '
  DECLARE
    formatted_string text := '''';
  BEGIN
    FOR i IN 0 .. length($1) LOOP
      IF substr($1, i, 1) = ''
'' THEN
        formatted_string := formatted_string || ''<br>'';
      ELSE
        formatted_string := formatted_string || substr($1, i, 1);
      END IF;
    END LOOP;
    RETURN formatted_string;
  END;
' LANGUAGE 'plpgsql';

--
-- TOC Entry ID 37 (OID 3751599)
--
-- Name: text_sorting Type: TABLE Owner: postgres
--

CREATE TABLE "text_sorting" (
	"letter" character(1)
);

--
-- TOC Entry ID 38 (OID 3751882)
--
-- Name: subjects Type: TABLE Owner: postgres
--

CREATE TABLE "subjects" (
	"id" integer NOT NULL,
	"subject" text,
	"location" text,
	Constraint "subjects_pkey" Primary Key ("id")
);

--
-- TOC Entry ID 108 (OID 3751924)
--
-- Name: sum(text) Type: AGGREGATE Owner: postgres
--

CREATE AGGREGATE sum ( BASETYPE = text, SFUNC = textcat, STYPE = text, INITCOND = '' );

--
-- TOC Entry ID 39 (OID 3751975)
--
-- Name: alternate_stock Type: TABLE Owner: postgres
--

CREATE TABLE "alternate_stock" (
	"isbn" text,
	"cost" numeric(5,2),
	"retail" numeric(5,2),
	"stock" integer
);

--
-- TOC Entry ID 40 (OID 3752020)
--
-- Name: book_backup Type: TABLE Owner: postgres
--

CREATE TABLE "book_backup" (
	"id" integer,
	"title" text,
	"author_id" integer,
	"subject_id" integer
);

--
-- TOC Entry ID 80 (OID 3752102)
--
-- Name: "sync_authors_and_books" () Type: FUNCTION Owner: postgres
--

CREATE FUNCTION "sync_authors_and_books" () RETURNS opaque AS '
  BEGIN
    IF TG_OP = ''UPDATE'' THEN
      UPDATE books SET author_id = new.id WHERE author_id = old.id; 
    END IF;
    RETURN new;
  END;
' LANGUAGE 'plpgsql';

--
-- TOC Entry ID 41 (OID 4063343)
--
-- Name: schedules Type: TABLE Owner: postgres
--
 
CREATE TABLE "schedules" (
        "employee_id" integer NOT NULL,
        "schedule" text,
        Constraint "schedules_pkey" Primary Key ("employee_id")
);

--
-- TOC Entry ID 42 (OID 4063653)
--
-- Name: recent_shipments Type: VIEW Owner: postgres
--

CREATE VIEW "recent_shipments" as SELECT count(*) AS num_shipped, max(shipments.ship_date) AS max, b.title FROM ((shipments JOIN editions USING (isbn)) NATURAL JOIN books b(book_id)) GROUP BY b.title ORDER BY count(*) DESC;

--
-- Data for TOC Entry ID 112 (OID 3117548)
--
-- Name: publishers Type: TABLE DATA Owner: postgres
--


COPY "publishers"  FROM stdin;
150	Kids Can Press	Kids Can Press, 29 Birch Ave. Toronto,�ON��M4V 1E2
91	Henry Holt & Company, Inc.	Henry Holt & Company, Inc. 115 West 18th Street New York, NY 10011
113	O'Reilly & Associates	O'Reilly & Associates, Inc. 101 Morris St, Sebastopol, CA 95472
62	Watson-Guptill Publications	1515 Boradway, New York, NY 10036
105	Noonday Press	Farrar Straus & Giroux Inc, 19 Union Square W, New York, NY 10003
99	Ace Books	The Berkley Publishing Group, Penguin Putnam Inc, 375 Hudson St, New York, NY 10014
101	Roc	Penguin Putnam Inc, 375 Hudson St, New York, NY 10014
163	Mojo Press	Mojo Press, PO Box 1215, Dripping Springs, TX 78720
171	Books of Wonder	Books of Wonder, 16 W. 18th St. New York, NY, 10011
102	Penguin	Penguin Putnam Inc, 375 Hudson St, New York, NY 10014
75	Doubleday	Random House, Inc, 1540 Broadway, New York, NY 10036
65	HarperCollins	HarperCollins Publishers, 10 E 53rd St, New York, NY 10022
59	Random House	Random House, Inc, 1540 Broadway, New York, NY 10036
\.
--
-- Data for TOC Entry ID 113 (OID 3389594)
--
-- Name: authors Type: TABLE DATA Owner: manager
--


COPY "authors"  FROM stdin;
1111	Denham	Ariel
1212	Worsley	John
15990	Bourgeois	Paulette
25041	Bianco	Margery Williams
16	Alcott	Louisa May
4156	King	Stephen
1866	Herbert	Frank
1644	Hogarth	Burne
2031	Brown	Margaret Wise
115	Poe	Edgar Allen
7805	Lutz	Mark
7806	Christiansen	Tom
1533	Brautigan	Richard
1717	Brite	Poppy Z.
2112	Gorey	Edward
2001	Clarke	Arthur C.
1213	Brookins	Andrew
\.
--
-- Data for TOC Entry ID 114 (OID 3389632)
--
-- Name: states Type: TABLE DATA Owner: postgres
--


COPY "states"  FROM stdin;
42	Washington	WA
51	Oregon	OR
\.
--
-- Data for TOC Entry ID 115 (OID 3389702)
--
-- Name: my_list Type: TABLE DATA Owner: postgres
--


COPY "my_list"  FROM stdin;
Pick up laundry.
Send out bills.
Wrap up Grand Unifying Theory for publication.
\.
--
-- Data for TOC Entry ID 116 (OID 3390348)
--
-- Name: stock Type: TABLE DATA Owner: postgres
--


COPY "stock"  FROM stdin;
0385121679	29.00	36.95	65
039480001X	30.00	32.95	31
0394900014	23.00	23.95	0
044100590X	36.00	45.95	89
0441172717	17.00	21.95	77
0451160916	24.00	28.95	22
0451198492	36.00	46.95	0
0451457994	17.00	22.95	0
0590445065	23.00	23.95	10
0679803335	20.00	24.95	18
0694003611	25.00	28.95	50
0760720002	18.00	23.95	28
0823015505	26.00	28.95	16
0929605942	19.00	21.95	25
1885418035	23.00	24.95	77
0394800753	16.00	16.95	4
\.
--
-- Data for TOC Entry ID 117 (OID 3390653)
--
-- Name: numeric_values Type: TABLE DATA Owner: postgres
--


COPY "numeric_values"  FROM stdin;
68719476736.000000
68719476737.000000
6871947673778.000000
999999999999999999999999.999900
999999999999999999999999.999999
-999999999999999999999999.999999
-100000000000000000000000.999999
1.999999
2.000000
2.000000
999999999999999999999999.999999
999999999999999999999999.000000
\.
--
-- Data for TOC Entry ID 118 (OID 3390866)
--
-- Name: daily_inventory Type: TABLE DATA Owner: postgres
--


COPY "daily_inventory"  FROM stdin;
039480001X	t
044100590X	t
0451198492	f
0394900014	f
0441172717	t
0451160916	f
0385121679	\N
\.
--
-- Data for TOC Entry ID 119 (OID 3391084)
--
-- Name: money_example Type: TABLE DATA Owner: postgres
--


COPY "money_example"  FROM stdin;
$12.24	12.24
\.
--
-- Data for TOC Entry ID 120 (OID 3391184)
--
-- Name: shipments Type: TABLE DATA Owner: postgres
--


COPY "shipments"  FROM stdin;
375	142	039480001X	2001-08-06 09:29:21-07
323	671	0451160916	2001-08-14 10:36:41-07
998	1045	0590445065	2001-08-12 12:09:47-07
749	172	0694003611	2001-08-11 10:52:34-07
662	655	0679803335	2001-08-09 07:30:07-07
806	1125	0760720002	2001-08-05 09:34:04-07
102	146	0394900014	2001-08-11 13:34:08-07
813	112	0385121679	2001-08-08 09:53:46-07
652	724	1885418035	2001-08-14 13:41:39-07
599	430	0929605942	2001-08-10 08:29:42-07
969	488	0441172717	2001-08-14 08:42:58-07
433	898	044100590X	2001-08-12 08:46:35-07
660	409	0451457994	2001-08-07 11:56:42-07
310	738	0451198492	2001-08-15 14:02:01-07
510	860	0823015505	2001-08-14 07:33:47-07
997	185	039480001X	2001-08-10 13:47:52-07
999	221	0451160916	2001-08-14 13:45:51-07
56	880	0590445065	2001-08-14 13:49:00-07
72	574	0694003611	2001-08-06 07:49:44-07
146	270	039480001X	2001-08-13 09:42:10-07
981	652	0451160916	2001-08-08 08:36:44-07
95	480	0590445065	2001-08-10 07:29:52-07
593	476	0694003611	2001-08-15 11:57:40-07
977	853	0679803335	2001-08-09 09:30:46-07
117	185	0760720002	2001-08-07 13:00:48-07
406	1123	0394900014	2001-08-13 09:47:04-07
340	1149	0385121679	2001-08-12 13:39:22-07
871	388	1885418035	2001-08-07 11:31:57-07
1000	221	039480001X	2001-09-14 16:46:32-07
1001	107	039480001X	2001-09-14 17:42:22-07
754	107	0394800753	2001-08-11 09:55:05-07
458	107	0394800753	2001-08-07 10:58:36-07
189	107	0394800753	2001-08-06 11:46:36-07
720	107	0394800753	2001-08-08 10:46:13-07
1002	107	0394800753	2001-09-22 11:23:28-07
2	107	0394800753	2001-09-22 20:58:56-07
\.
--
-- Data for TOC Entry ID 121 (OID 3391454)
--
-- Name: customers Type: TABLE DATA Owner: manager
--


COPY "customers"  FROM stdin;
107	Jackson	Annie
112	Gould	Ed
142	Allen	Chad
146	Williams	James
172	Brown	Richard
185	Morrill	Eric
221	King	Jenny
270	Bollman	Julie
388	Morrill	Royce
409	Holloway	Christine
430	Black	Jean
476	Clark	James
480	Thomas	Rich
488	Young	Trevor
574	Bennett	Laura
652	Anderson	Jonathan
655	Olson	Dave
671	Brown	Chuck
723	Eisele	Don
724	Holloway	Adam
738	Gould	Shirley
830	Robertson	Royce
853	Black	Wendy
860	Owens	Tim
880	Robinson	Tammy
898	Gerdes	Kate
964	Gould	Ramon
1045	Owens	Jean
1125	Bollman	Owen
1149	Becker	Owen
1123	Corner	Kathy
\.
--
-- Data for TOC Entry ID 122 (OID 3574043)
--
-- Name: book_queue Type: TABLE DATA Owner: postgres
--


COPY "book_queue"  FROM stdin;
Learning Python	7805	4	t
Perl Cookbook	7806	4	t
\.
--
-- Data for TOC Entry ID 123 (OID 3574983)
--
-- Name: stock_backup Type: TABLE DATA Owner: postgres
--


COPY "stock_backup"  FROM stdin;
0385121679	29.00	36.95	65
039480001X	30.00	32.95	31
0394800753	16.00	16.95	0
0394900014	23.00	23.95	0
044100590X	36.00	45.95	89
0441172717	17.00	21.95	77
0451160916	24.00	28.95	22
0451198492	36.00	46.95	0
0451457994	17.00	22.95	0
0590445065	23.00	23.95	10
0679803335	20.00	24.95	18
0694003611	25.00	28.95	50
0760720002	18.00	23.95	28
0823015505	26.00	28.95	16
0929605942	19.00	21.95	25
1885418035	23.00	24.95	77
\.
--
-- Data for TOC Entry ID 124 (OID 3628247)
--
-- Name: favorite_books Type: TABLE DATA Owner: manager
--


COPY "favorite_books"  FROM stdin;
102	{"The Hitchhiker's Guide to the Galaxy","The Restauraunt at the End of the Universe"}
103	{"There and Back Again: A Hobbit's Holiday","Kittens Squared"}
\.
--
-- Data for TOC Entry ID 125 (OID 3628899)
--
-- Name: employees Type: TABLE DATA Owner: postgres
--


COPY "employees"  FROM stdin;
101	Appel	Vincent
102	Holloway	Michael
105	Connoly	Sarah
104	Noble	Ben
103	Joble	David
106	Hall	Timothy
1008	Williams	\N
\.
--
-- Data for TOC Entry ID 126 (OID 3629174)
--
-- Name: editions Type: TABLE DATA Owner: manager
--


COPY "editions"  FROM stdin;
039480001X	1608	1	59	1957-03-01	h
0451160916	7808	1	75	1981-08-01	p
0394800753	1590	1	59	1949-03-01	p
0590445065	25908	1	150	1987-03-01	p
0694003611	1501	1	65	1947-03-04	p
0679803335	1234	1	102	1922-01-01	p
0760720002	190	1	91	1868-01-01	p
0394900014	1608	1	59	1957-01-01	p
0385121679	7808	2	75	1993-10-01	h
1885418035	156	1	163	1995-03-28	p
0929605942	156	2	171	1998-12-01	p
0441172717	4513	2	99	1998-09-01	p
044100590X	4513	3	99	1999-10-01	h
0451457994	4267	3	101	2000-09-12	p
0451198492	4267	3	101	1999-10-01	h
0823015505	2038	1	62	1958-01-01	p
0596000855	41473	2	113	2001-03-01	p
\.
--
-- Data for TOC Entry ID 127 (OID 3629264)
--
-- Name: books Type: TABLE DATA Owner: manager
--


COPY "books"  FROM stdin;
7808	The Shining	4156	9
4513	Dune	1866	15
4267	2001: A Space Odyssey	2001	15
1608	The Cat in the Hat	1809	2
1590	Bartholomew and the Oobleck	1809	2
25908	Franklin in the Dark	15990	2
1501	Goodnight Moon	2031	2
190	Little Women	16	6
1234	The Velveteen Rabbit	25041	3
2038	Dynamic Anatomy	1644	0
156	The Tell-Tale Heart	115	9
41473	Programming Python	7805	4
41477	Learning Python	7805	4
41478	Perl Cookbook	7806	4
41472	Practical PostgreSQL	1212	4
\.
--
-- Data for TOC Entry ID 128 (OID 3629424)
--
-- Name: distinguished_authors Type: TABLE DATA Owner: manager
--


COPY "distinguished_authors"  FROM stdin;
25043	Simon	Neil	Pulitzer Prize
1809	Geisel	Theodor Seuss	Pulitzer Prize
\.
--
-- Data for TOC Entry ID 129 (OID 3727889)
--
-- Name: favorite_authors Type: TABLE DATA Owner: manager
--


COPY "favorite_authors"  FROM stdin;
102	{{"J.R.R. Tolkien","The Silmarillion"},{"Charles Dickens","Great Expectations"},{"Ariel Denham","Attic Lives"}}
\.
--
-- Data for TOC Entry ID 130 (OID 3751599)
--
-- Name: text_sorting Type: TABLE DATA Owner: postgres
--


COPY "text_sorting"  FROM stdin;
0
1
2
3
A
B
C
D
a
b
c
d
\.
--
-- Data for TOC Entry ID 131 (OID 3751882)
--
-- Name: subjects Type: TABLE DATA Owner: postgres
--


COPY "subjects"  FROM stdin;
0	Arts	Creativity St
1	Business	Productivity Ave
2	Children's Books	Kids Ct
3	Classics	Academic Rd
4	Computers	Productivity Ave
5	Cooking	Creativity St
6	Drama	Main St
7	Entertainment	Main St
8	History	Academic Rd
9	Horror	Black Raven Dr
10	Mystery	Black Raven Dr
11	Poetry	Sunset Dr
12	Religion	\N
13	Romance	Main St
14	Science	Productivity Ave
15	Science Fiction	Main St
\.
--
-- Data for TOC Entry ID 132 (OID 3751975)
--
-- Name: alternate_stock Type: TABLE DATA Owner: postgres
--


COPY "alternate_stock"  FROM stdin;
0385121679	29.00	36.95	65
039480001X	30.00	32.95	31
0394900014	23.00	23.95	0
044100590X	36.00	45.95	89
0441172717	17.00	21.95	77
0451160916	24.00	28.95	22
0451198492	36.00	46.95	0
0451457994	17.00	22.95	0
0590445065	23.00	23.95	10
0679803335	20.00	24.95	18
0694003611	25.00	28.95	50
0760720002	18.00	23.95	28
0823015505	26.00	28.95	16
0929605942	19.00	21.95	25
1885418035	23.00	24.95	77
0394800753	16.00	16.95	4
\.
--
-- Data for TOC Entry ID 133 (OID 3752020)
--
-- Name: book_backup Type: TABLE DATA Owner: postgres
--


COPY "book_backup"  FROM stdin;
7808	The Shining	4156	9
4513	Dune	1866	15
4267	2001: A Space Odyssey	2001	15
1608	The Cat in the Hat	1809	2
1590	Bartholomew and the Oobleck	1809	2
25908	Franklin in the Dark	15990	2
1501	Goodnight Moon	2031	2
190	Little Women	16	6
1234	The Velveteen Rabbit	25041	3
2038	Dynamic Anatomy	1644	0
156	The Tell-Tale Heart	115	9
41472	Practical PostgreSQL	1212	4
41473	Programming Python	7805	4
41477	Learning Python	7805	4
41478	Perl Cookbook	7806	4
7808	The Shining	4156	9
4513	Dune	1866	15
4267	2001: A Space Odyssey	2001	15
1608	The Cat in the Hat	1809	2
1590	Bartholomew and the Oobleck	1809	2
25908	Franklin in the Dark	15990	2
1501	Goodnight Moon	2031	2
190	Little Women	16	6
1234	The Velveteen Rabbit	25041	3
2038	Dynamic Anatomy	1644	0
156	The Tell-Tale Heart	115	9
41473	Programming Python	7805	4
41477	Learning Python	7805	4
41478	Perl Cookbook	7806	4
41472	Practical PostgreSQL	1212	4
\.
--
-- Data for TOC Entry ID 134 (OID 4063343)
--
-- Name: schedules Type: TABLE DATA Owner: postgres
--


COPY "schedules"  FROM stdin;
102	Mon - Fri, 9am - 5pm
\.
--
-- TOC Entry ID 45 (OID 3117548)
--
-- Name: "unique_publisher_idx" Type: INDEX Owner: postgres
--

CREATE UNIQUE INDEX "unique_publisher_idx" on "publishers" using btree ( "name" "text_ops" );

--
-- TOC Entry ID 43 (OID 3391184)
--
-- Name: "shipments_ship_id_key" Type: INDEX Owner: postgres
--

CREATE UNIQUE INDEX "shipments_ship_id_key" on "shipments" using btree ( "id" "int4_ops" );

--
-- TOC Entry ID 44 (OID 3629264)
--
-- Name: "books_title_idx" Type: INDEX Owner: manager
--

CREATE  INDEX "books_title_idx" on "books" using btree ( "title" "text_ops" );

--
-- TOC Entry ID 46 (OID 3751599)
--
-- Name: "text_idx" Type: INDEX Owner: postgres
--

CREATE  INDEX "text_idx" on "text_sorting" using btree ( "letter" "bpchar_ops" );

--
-- TOC Entry ID 136 (OID 3628649)
--
-- Name: check_shipment Type: TRIGGER Owner: postgres
--

CREATE TRIGGER "check_shipment" BEFORE INSERT OR UPDATE ON "shipments"  FOR EACH ROW EXECUTE PROCEDURE "check_shipment_addition" ();

--
-- TOC Entry ID 135 (OID 3752103)
--
-- Name: sync_authors_books Type: TRIGGER Owner: manager
--

CREATE TRIGGER "sync_authors_books" BEFORE UPDATE ON "authors"  FOR EACH ROW EXECUTE PROCEDURE "sync_authors_and_books" ();

--
-- TOC Entry ID 139 (OID 4063374)
--
-- Name: "RI_ConstraintTrigger_4063373" Type: TRIGGER Owner: postgres
--

CREATE CONSTRAINT TRIGGER "valid_employee" AFTER INSERT OR UPDATE ON "schedules"  FROM "employees" NOT DEFERRABLE INITIALLY IMMEDIATE FOR EACH ROW EXECUTE PROCEDURE "RI_FKey_check_ins" ('valid_employee', 'schedules', 'employees', 'FULL', 'employee_id', 'id');

--
-- TOC Entry ID 137 (OID 4063376)
--
-- Name: "RI_ConstraintTrigger_4063375" Type: TRIGGER Owner: postgres
--

CREATE CONSTRAINT TRIGGER "valid_employee" AFTER DELETE ON "employees"  FROM "schedules" NOT DEFERRABLE INITIALLY IMMEDIATE FOR EACH ROW EXECUTE PROCEDURE "RI_FKey_noaction_del" ('valid_employee', 'schedules', 'employees', 'FULL', 'employee_id', 'id');

--
-- TOC Entry ID 138 (OID 4063378)
--
-- Name: "RI_ConstraintTrigger_4063377" Type: TRIGGER Owner: postgres
--

CREATE CONSTRAINT TRIGGER "valid_employee" AFTER UPDATE ON "employees"  FROM "schedules" NOT DEFERRABLE INITIALLY IMMEDIATE FOR EACH ROW EXECUTE PROCEDURE "RI_FKey_noaction_upd" ('valid_employee', 'schedules', 'employees', 'FULL', 'employee_id', 'id');

--
-- TOC Entry ID 140 (OID 3752079)
--
-- Name: sync_stock_with_editions Type: RULE Owner: manager
--

CREATE RULE sync_stock_with_editions AS ON UPDATE TO editions DO UPDATE stock SET isbn = new.isbn WHERE (stock.isbn = old.isbn);
--
-- TOC Entry ID 5 (OID 3390416)
--
-- Name: subject_ids Type: SEQUENCE SET Owner: 
--

SELECT setval ('"subject_ids"', 15, 't');

--
-- TOC Entry ID 7 (OID 3574018)
--
-- Name: book_ids Type: SEQUENCE SET Owner: 
--

SELECT setval ('"book_ids"', 41478, 't');

--
-- TOC Entry ID 9 (OID 3628626)
--
-- Name: shipments_ship_id_seq Type: SEQUENCE SET Owner: 
--

SELECT setval ('"shipments_ship_id_seq"', 1011, 't');

--
-- TOC Entry ID 11 (OID 3629402)
--
-- Name: author_ids Type: SEQUENCE SET Owner: 
--

SELECT setval ('"author_ids"', 25044, 't');



================================================
FILE: data/invalid.toml
================================================
invalid encoding

================================================
FILE: data/lc_example1.sql
================================================
-- pgweb: host="localhost"
select 'foo'


================================================
FILE: data/lc_example2.sql
================================================
-- pgweb: host="localhost"
-- some comment
-- pgweb: user="foo"

select 'foo'


================================================
FILE: data/lc_invalid_meta.sql
================================================
-- pgweb: host="localhost" mode="foo"
select 'foo'


================================================
FILE: data/lc_no_meta.sql
================================================
select 'foo'


================================================
FILE: data/passfile
================================================
localhost:5432:dbname:username:password
127.0.0.1:5432:*:*:password2


================================================
FILE: data/roach.sql
================================================
DROP DATABASE IF EXISTS "roach";
CREATE DATABASE "roach";
USE "roach";

CREATE TABLE product_information (
  product_id           INT PRIMARY KEY NOT NULL,
  product_name         STRING(50) UNIQUE NOT NULL,
  product_description  STRING(2000),
  category_id          STRING(1) NOT NULL CHECK (category_id IN ('A','B','C')),
  weight_class         INT,
  warranty_period      INT CONSTRAINT valid_warranty CHECK (warranty_period BETWEEN 0 AND 24),
  supplier_id          INT,
  product_status       STRING(20),
  list_price           DECIMAL(8,2),
  min_price            DECIMAL(8,2),
  catalog_url          STRING(50) UNIQUE,
  date_added           DATE DEFAULT CURRENT_DATE(),
  misc                 JSONB,     
  CONSTRAINT price_check CHECK (list_price >= min_price),
  INDEX date_added_idx (date_added),
  INDEX supp_id_prod_status_idx (supplier_id, product_status),
  INVERTED INDEX details (misc)
);

INSERT INTO product_information VALUES
  (1, 'Product A', 'Text', 'A', NULL, 1),
  (2, 'Product B', 'Text', 'B', NULL, 2),
  (3, 'Product C', 'Text', 'C', NULL, 3);

CREATE TABLE customers (
  id INT PRIMARY KEY,
  name STRING
);

CREATE TABLE orders (
  id INT PRIMARY KEY,
  customer_id INT REFERENCES customers(id) ON DELETE CASCADE
);

INSERT INTO customers VALUES (1, 'Lauren');
INSERT INTO orders VALUES (1,1);
DELETE FROM customers WHERE id = 1;
SELECT * FROM orders;

================================================
FILE: docker-compose-pg.yml
================================================
---
x-base: &base
  environment: &env
    POSTGRES_DB: pgweb
    POSTGRES_PASSWORD: pgweb
    POSTGRES_USER: pgweb
  healthcheck:
    test: pg_isready -U pgweb -h 127.0.0.1
    interval: 5s

services:
  postgres18:
    <<: *base
    image: postgres:18
    ports:
      - 5433:5432
  postgres17:
    <<: *base
    image: postgres:17
    ports:
      - 5433:5432
  postgres16:
    <<: *base
    image: postgres:16
    ports:
      - 5433:5432
  postgres15:
    <<: *base
    image: postgres:15
    ports:
      - 5433:5432
  postgres14:
    <<: *base
    image: postgres:14
    ports:
      - 5434:5432
  postgres13:
    <<: *base
    image: postgres:13
    ports:
      - 5435:5432
  postgres12:
    <<: *base
    image: postgres:12
    ports:
      - 5436:5432
  postgres11:
    <<: *base
    image: postgres:11
    ports:
      - 5437:5432
  postgres10:
    <<: *base
    image: postgres:10
    ports:
      - 5438:5432
  postgres9.6:
    <<: *base
    image: postgres:9.6
    ports:
      - 5439:5432


================================================
FILE: docker-compose.yml
================================================
---
services:
  postgres:
    container_name: pgweb-postgres
    image: postgres:15
    ports:
      - 5433:5432
    volumes:
      - data:/var/lib/postgresql/data
    environment:
      POSTGRES_DB: pgweb
      POSTGRES_PASSWORD: pgweb
      POSTGRES_USER: pgweb
    healthcheck:
      test: pg_isready -U pgweb -h 127.0.0.1
      interval: 5s
    networks:
      - pgweb

  pgweb:
    container_name: pgweb
    image: sosedoff/pgweb:latest
    build: .
    environment:
      PGWEB_DATABASE_URL: postgres://pgweb:pgweb@pgweb-postgres:5432/pgweb?sslmode=disable
    ports:
      - 8081:8081
    networks:
      - pgweb
    depends_on:
      postgres:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "nc", "-vz", "127.0.0.1", "8081"]
      interval: 5s

volumes:
  data:
    name: pgweb_postgres

networks:
  pgweb:
    name: pgweb


================================================
FILE: fly.toml
================================================
app = "pgweb-demo"
kill_signal = "SIGINT"
kill_timeout = 5

[processes]
  web = "pgweb --sessions --bind=0.0.0.0 --metrics --idle-timeout=30"

[[services]]
  http_checks = []
  internal_port = 8081
  processes = ["web"]
  protocol = "tcp"
  script_checks = []

  [services.concurrency]
    hard_limit = 25
    soft_limit = 20
    type = "connections"

  [[services.ports]]
    force_https = true
    handlers = ["http"]
    port = 80

  [[services.ports]]
    handlers = ["tls", "http"]
    port = 443

  [[services.tcp_checks]]
    grace_period = "1s"
    interval = "15s"
    restart_limit = 0
    timeout = "2s"

[metrics]
  port = 8081
  path = "/metrics"


================================================
FILE: go.mod
================================================
module github.com/sosedoff/pgweb

go 1.25

toolchain go1.25.4

require (
	github.com/BurntSushi/toml v1.1.0
	github.com/ScaleFT/sshkeys v0.0.0-20200327173127-6142f742bca5
	github.com/gin-gonic/gin v1.11.0
	github.com/jackc/pgpassfile v1.0.0
	github.com/jessevdk/go-flags v1.5.0
	github.com/jmoiron/sqlx v1.3.5
	github.com/lib/pq v1.10.5
	github.com/mitchellh/go-homedir v1.1.0
	github.com/mr-tron/base58 v1.2.0
	github.com/prometheus/client_golang v1.19.1
	github.com/sirupsen/logrus v1.9.1
	github.com/stretchr/testify v1.11.1
	github.com/tuvistavie/securerandom v0.0.0-20140719024926-15512123a948
	golang.org/x/crypto v0.46.0
)

require (
	github.com/beorn7/perks v1.0.1 // indirect
	github.com/bytedance/gopkg v0.1.3 // indirect
	github.com/bytedance/sonic v1.14.2 // indirect
	github.com/bytedance/sonic/loader v0.4.0 // indirect
	github.com/cespare/xxhash/v2 v2.2.0 // indirect
	github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
	github.com/cloudwego/base64x v0.1.6 // indirect
	github.com/davecgh/go-spew v1.1.1 // indirect
	github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a // indirect
	github.com/gabriel-vasile/mimetype v1.4.12 // indirect
	github.com/gin-contrib/sse v1.1.0 // indirect
	github.com/go-playground/locales v0.14.1 // indirect
	github.com/go-playground/universal-translator v0.18.1 // indirect
	github.com/go-playground/validator/v10 v10.30.1 // indirect
	github.com/goccy/go-json v0.10.5 // indirect
	github.com/goccy/go-yaml v1.19.1 // indirect
	github.com/golang/protobuf v1.5.3 // indirect
	github.com/json-iterator/go v1.1.12 // indirect
	github.com/klauspost/cpuid/v2 v2.3.0 // indirect
	github.com/kr/pretty v0.3.1 // indirect
	github.com/leodido/go-urn v1.4.0 // indirect
	github.com/mattn/go-isatty v0.0.20 // indirect
	github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
	github.com/modern-go/reflect2 v1.0.2 // indirect
	github.com/pelletier/go-toml/v2 v2.2.4 // indirect
	github.com/pmezard/go-difflib v1.0.0 // indirect
	github.com/prometheus/client_model v0.5.0 // indirect
	github.com/prometheus/common v0.48.0 // indirect
	github.com/prometheus/procfs v0.12.0 // indirect
	github.com/quic-go/qpack v0.6.0 // indirect
	github.com/quic-go/quic-go v0.58.0 // indirect
	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
	github.com/ugorji/go/codec v1.3.1 // indirect
	go.uber.org/mock v0.6.0 // indirect
	golang.org/x/arch v0.23.0 // indirect
	golang.org/x/mod v0.31.0 // indirect
	golang.org/x/net v0.48.0 // indirect
	golang.org/x/sync v0.19.0 // indirect
	golang.org/x/sys v0.39.0 // indirect
	golang.org/x/text v0.32.0 // indirect
	golang.org/x/tools v0.40.0 // indirect
	google.golang.org/protobuf v1.36.11 // indirect
	gopkg.in/yaml.v3 v3.0.1 // indirect
)


================================================
FILE: go.sum
================================================
github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I=
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/ScaleFT/sshkeys v0.0.0-20200327173127-6142f742bca5 h1:VauE2GcJNZFun2Och6tIT2zJZK1v6jxALQDA9BIji/E=
github.com/ScaleFT/sshkeys v0.0.0-20200327173127-6142f742bca5/go.mod h1:gxOHeajFfvGQh/fxlC8oOKBe23xnnJTif00IFFbiT+o=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
github.com/bytedance/sonic v1.14.2 h1:k1twIoe97C1DtYUo+fZQy865IuHia4PR5RPiuGPPIIE=
github.com/bytedance/sonic v1.14.2/go.mod h1:T80iDELeHiHKSc0C9tubFygiuXoGzrkjKzX2quAx980=
github.com/bytedance/sonic/loader v0.4.0 h1:olZ7lEqcxtZygCK9EKYKADnpQoYkRQxaeY2NYzevs+o=
github.com/bytedance/sonic/loader v0.4.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a h1:saTgr5tMLFnmy/yg3qDTft4rE5DY2uJ/cCxCe3q0XTU=
github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a/go.mod h1:Bw9BbhOJVNR+t0jCqx2GC6zv0TGBsShs56Y3gfSCvl0=
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw=
github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w=
github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-yaml v1.19.1 h1:3rG3+v8pkhRqoQ/88NYNMHYVGYztCOCIZ7UQhu7H+NE=
github.com/goccy/go-yaml v1.19.1/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc=
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.5 h1:J+gdV2cUmX7ZqL2B0lFcW0m+egaHC2V3lpO8nWxyYiQ=
github.com/lib/pq v1.10.5/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg=
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw=
github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y=
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
github.com/prometheus/common v0.39.0 h1:oOyhkDq05hPZKItWVBkJ6g6AtGxi+fy7F4JvUV8uhsI=
github.com/prometheus/common v0.39.0/go.mod h1:6XBZ7lYdLCbkAVhwRsWTZn+IN5AB9F/NXd5w0BbEX0Y=
github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE=
github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
github.com/quic-go/quic-go v0.58.0 h1:ggY2pvZaVdB9EyojxL1p+5mptkuHyX5MOSv4dgWF4Ug=
github.com/quic-go/quic-go v0.58.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/sirupsen/logrus v1.9.1 h1:Ou41VVR3nMWWmTiEUnj0OlsgOSCUFgsPAOl6jRIcVtQ=
github.com/sirupsen/logrus v1.9.1/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tuvistavie/securerandom v0.0.0-20140719024926-15512123a948 h1:yL0l/u242MzDP6D0B5vGC+wxm5WRY+alQZy+dJk3bFI=
github.com/tuvistavie/securerandom v0.0.0-20140719024926-15512123a948/go.mod h1:a06d/M1pxWi51qiSrfGMHaEydtuXT06nha8N2aNQuXk=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY=
github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.23.0 h1:lKF64A2jF6Zd8L0knGltUnegD62JMFBiCPBmQpToHhg=
golang.org/x/arch v0.23.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200219091948-cb0a6d8edb6c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=


================================================
FILE: main.go
================================================
package main

import (
	"github.com/sosedoff/pgweb/pkg/cli"
)

func main() {
	cli.Run()
}


================================================
FILE: pkg/api/api.go
================================================
package api

import (
	"context"
	"encoding/base64"
	"fmt"
	"net/http"
	neturl "net/url"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/tuvistavie/securerandom"

	"github.com/sosedoff/pgweb/pkg/bookmarks"
	"github.com/sosedoff/pgweb/pkg/client"
	"github.com/sosedoff/pgweb/pkg/command"
	"github.com/sosedoff/pgweb/pkg/connect"
	"github.com/sosedoff/pgweb/pkg/connection"
	"github.com/sosedoff/pgweb/pkg/metrics"
	"github.com/sosedoff/pgweb/pkg/queries"
	"github.com/sosedoff/pgweb/pkg/shared"
	"github.com/sosedoff/pgweb/static"
)

var (
	// DbClient represents the active database connection in a single-session mode
	DbClient *client.Client

	// DbSessions represents the mapping for client connections
	DbSessions *SessionManager

	// QueryStore reads the SQL queries stores in the home directory
	QueryStore *queries.Store
)

// DB returns a database connection from the client context
func DB(c *gin.Context) *client.Client {
	if command.Opts.Sessions {
		return DbSessions.Get(getSessionId(c.Request))
	}
	return DbClient
}

// setClient sets the database client connection for the sessions
func setClient(c *gin.Context, newClient *client.Client) error {
	currentClient := DB(c)
	if currentClient != nil {
		currentClient.Close()
	}

	if !command.Opts.Sessions {
		DbClient = newClient
		return nil
	}

	sid := getSessionId(c.Request)
	if sid == "" {
		return errSessionRequired
	}

	DbSessions.Add(sid, newClient)
	return nil
}

// GetHome renders the home page
func GetHome(prefix string) http.Handler {
	if prefix != "" {
		prefix = "/" + prefix
	}
	return http.StripPrefix(prefix, static.GetHandler())
}

func GetAssets(prefix string) http.Handler {
	if prefix != "" {
		prefix = "/" + prefix + "static/"
	} else {
		prefix = "/static/"
	}
	return http.StripPrefix(prefix, static.GetHandler())
}

// GetSessions renders the number of active sessions
func GetSessions(c *gin.Context) {
	// In debug mode endpoint will return a lot of sensitive information
	// like full database connection string and all query history.
	if command.Opts.Debug {
		successResponse(c, DbSessions.Sessions())
		return
	}
	successResponse(c, gin.H{"sessions": DbSessions.Len()})
}

// ConnectWithBackend creates a new connection based on backend resource
func ConnectWithBackend(c *gin.Context) {
	backend := connect.NewBackend(command.Opts.ConnectBackend, command.Opts.ConnectToken)
	backend.SetLogger(logger)

	if command.Opts.ConnectHeaders != "" {
		backend.SetPassHeaders(strings.Split(command.Opts.ConnectHeaders, ","))
	}

	ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
	defer cancel()

	// Fetch connection credentials
	cred, err := backend.FetchCredential(ctx, c.Param("resource"), c.Request.Header)
	if err != nil {
		badRequest(c, err)
		return
	}

	// Make the new session
	sid, err := securerandom.Uuid()
	if err != nil {
		badRequest(c, err)
		return
	}
	c.Request.Header.Add("x-session-id", sid)

	// Connect to the database
	cl, err := client.NewFromUrl(cred.DatabaseURL, nil)
	if err != nil {
		badRequest(c, err)
		return
	}
	cl.External = true

	// Finalize session seetup
	_, err = cl.Info()
	if err == nil {
		err = setClient(c, cl)
	}
	if err != nil {
		cl.Close()
		badRequest(c, err)
		return
	}

	redirectURI := fmt.Sprintf("/%s?session=%s", command.Opts.Prefix, sid)
	c.Redirect(302, redirectURI)
}

// Connect creates a new client connection
func Connect(c *gin.Context) {
	if command.Opts.LockSession {
		badRequest(c, errSessionLocked)
		return
	}

	var (
		cl  *client.Client
		err error
	)

	if bookmarkID := c.Request.FormValue("bookmark_id"); bookmarkID != "" {
		cl, err = ConnectWithBookmark(bookmarkID)
	} else if command.Opts.BookmarksOnly {
		err = errNotPermitted
	} else {
		cl, err = ConnectWithURL(c)
	}
	if err != nil {
		badRequest(c, err)
		return
	}

	err = cl.Test()
	if err != nil {
		badRequest(c, err)
		return
	}

	info, err := cl.Info()
	if err == nil {
		err = setClient(c, cl)
	}
	if err != nil {
		cl.Close()
		badRequest(c, err)
		return
	}

	successResponse(c, info.Format()[0])
}

func ConnectWithURL(c *gin.Context) (*client.Client, error) {
	url := c.Request.FormValue("url")
	if url == "" {
		return nil, errURLRequired
	}

	url, err := connection.FormatURL(command.Options{
		URL:      url,
		Passfile: command.Opts.Passfile,
	})
	if err != nil {
		return nil, err
	}

	var sshInfo *shared.SSHInfo
	if c.Request.FormValue("ssh") != "" {
		sshInfo = parseSshInfo(c)
	}

	return client.NewFromUrl(url, sshInfo)
}

func ConnectWithBookmark(id string) (*client.Client, error) {
	manager := bookmarks.NewManager(command.Opts.BookmarksDir)

	bookmark, err := manager.Get(id)
	if err != nil {
		return nil, err
	}

	return client.NewFromBookmark(bookmark)
}

// SwitchDb perform database switch for the client connection
func SwitchDb(c *gin.Context) {
	if command.Opts.LockSession {
		badRequest(c, errSessionLocked)
		return
	}

	name := c.Request.URL.Query().Get("db")
	if name == "" {
		name = c.Request.FormValue("db")
	}
	if name == "" {
		badRequest(c, errDatabaseNameRequired)
		return
	}

	conn := DB(c)
	if conn == nil {
		badRequest(c, errNotConnected)
		return
	}

	// Do not allow switching databases for connections from third-party backends
	if conn.External {
		badRequest(c, errSessionLocked)
		return
	}

	currentURL, err := neturl.Parse(conn.ConnectionString)
	if err != nil {
		badRequest(c, errInvalidConnString)
		return
	}
	currentURL.Path = name

	cl, err := client.NewFromUrl(currentURL.String(), nil)
	if err != nil {
		badRequest(c, err)
		return
	}

	err = cl.Test()
	if err != nil {
		badRequest(c, err)
		return
	}

	info, err := cl.Info()
	if err == nil {
		err = setClient(c, cl)
	}
	if err != nil {
		cl.Close()
		badRequest(c, err)
		return
	}

	conn.Close()

	successResponse(c, info.Format()[0])
}

// Disconnect closes the current database connection
func Disconnect(c *gin.Context) {
	if command.Opts.LockSession {
		badRequest(c, errSessionLocked)
		return
	}

	if command.Opts.Sessions {
		result := DbSessions.Remove(getSessionId(c.Request))
		successResponse(c, gin.H{"success": result})
		return
	}

	conn := DB(c)
	if conn == nil {
		badRequest(c, errNotConnected)
		return
	}

	err := conn.Close()
	if err != nil {
		badRequest(c, err)
		return
	}

	DbClient = nil
	successResponse(c, gin.H{"success": true})
}

// RunQuery executes the query
func RunQuery(c *gin.Context) {
	query := cleanQuery(c.Request.FormValue("query"))

	if query == "" {
		badRequest(c, errQueryRequired)
		return
	}

	HandleQuery(query, c)
}

// ExplainQuery renders query explain plan
func ExplainQuery(c *gin.Context) {
	query := cleanQuery(c.Request.FormValue("query"))

	if query == "" {
		badRequest(c, errQueryRequired)
		return
	}

	HandleQuery(fmt.Sprintf("EXPLAIN %s", query), c)
}

// AnalyzeQuery renders query explain plan and analyze profile
func AnalyzeQuery(c *gin.Context) {
	query := cleanQuery(c.Request.FormValue("query"))

	if query == "" {
		badRequest(c, errQueryRequired)
		return
	}

	HandleQuery(fmt.Sprintf("EXPLAIN ANALYZE %s", query), c)
}

// GetDatabases renders a list of all databases on the server
func GetDatabases(c *gin.Context) {
	if command.Opts.LockSession {
		serveResult(c, []string{}, nil)
		return
	}
	conn := DB(c)
	if conn.External {
		errorResponse(c, 403, errNotPermitted)
		return
	}

	names, err := DB(c).Databases()
	serveResult(c, names, err)
}

// GetObjects renders a list of database objects
func GetObjects(c *gin.Context) {
	result, err := DB(c).Objects()
	if err != nil {
		badRequest(c, err)
		return
	}
	successResponse(c, client.ObjectsFromResult(result))
}

// GetSchemas renders list of available schemas
func GetSchemas(c *gin.Context) {
	res, err := DB(c).Schemas()
	serveResult(c, res, err)
}

// GetTable renders table information
func GetTable(c *gin.Context) {
	var (
		res *client.Result
		err error
	)

	db := DB(c)
	tableName := c.Params.ByName("table")

	switch c.Request.FormValue("type") {
	case client.ObjTypeMaterializedView:
		res, err = db.MaterializedView(tableName)
	case client.ObjTypeFunction:
		res, err = db.Function(tableName)
	default:
		res, err = db.Table(tableName)
	}

	serveResult(c, res, err)
}

// GetTableRows renders table rows
func GetTableRows(c *gin.Context) {
	offset, err := parseIntFormValue(c, "offset", 0)
	if err != nil {
		badRequest(c, err)
		return
	}

	limit, err := parseIntFormValue(c, "limit", 100)
	if err != nil {
		badRequest(c, err)
		return
	}

	opts := client.RowsOptions{
		Limit:      limit,
		Offset:     offset,
		SortColumn: c.Request.FormValue("sort_column"),
		SortOrder:  c.Request.FormValue("sort_order"),
		Where:      c.Request.FormValue("where"),
	}

	res, err := DB(c).TableRows(c.Params.ByName("table"), opts)
	if err != nil {
		badRequest(c, err)
		return
	}

	countRes, err := DB(c).TableRowsCount(c.Params.ByName("table"), opts)
	if err != nil {
		badRequest(c, err)
		return
	}

	numFetch := int64(opts.Limit)
	numOffset := int64(opts.Offset)
	numRows := countRes.Rows[0][0].(int64)
	numPages := numRows / numFetch

	if numPages*numFetch < numRows {
		numPages++
	}

	res.Pagination = &client.Pagination{
		Rows:    numRows,
		Page:    (numOffset / numFetch) + 1,
		Pages:   numPages,
		PerPage: numFetch,
	}

	serveResult(c, res, err)
}

// GetTableInfo renders a selected table information
func GetTableInfo(c *gin.Context) {
	res, err := DB(c).TableInfo(c.Params.ByName("table"))
	if err == nil {
		successResponse(c, res.Format()[0])
	} else {
		badRequest(c, err)
	}
}

// GetHistory renders a list of recent queries
func GetHistory(c *gin.Context) {
	successResponse(c, DB(c).History)
}

// GetConnectionInfo renders information about current connection
func GetConnectionInfo(c *gin.Context) {
	conn := DB(c)

	if err := conn.TestWithTimeout(5 * time.Second); err != nil {
		badRequest(c, err)
		return
	}

	res, err := conn.Info()
	if err != nil {
		badRequest(c, err)
		return
	}

	info := res.Format()[0]
	info["session_lock"] = command.Opts.LockSession

	successResponse(c, info)
}

// GetServerSettings renders a list of all server settings
func GetServerSettings(c *gin.Context) {
	res, err := DB(c).ServerSettings()
	serveResult(c, res, err)
}

// GetActivity renders a list of running queries
func GetActivity(c *gin.Context) {
	res, err := DB(c).Activity()
	serveResult(c, res, err)
}

// GetTableIndexes renders a list of database table indexes
func GetTableIndexes(c *gin.Context) {
	res, err := DB(c).TableIndexes(c.Params.ByName("table"))
	serveResult(c, res, err)
}

// GetTableConstraints renders a list of database constraints
func GetTableConstraints(c *gin.Context) {
	res, err := DB(c).TableConstraints(c.Params.ByName("table"))
	serveResult(c, res, err)
}

// GetTablesStats renders data sizes and estimated rows for all tables in the database
func GetTablesStats(c *gin.Context) {
	db := DB(c)

	connCtx, err := db.GetConnContext()
	if err != nil {
		badRequest(c, err)
		return
	}

	res, err := db.TablesStats()
	if err != nil {
		badRequest(c, err)
		return
	}

	format := getQueryParam(c, "format")
	if format == "" {
		format = "json"
	}

	// Save as attachment if exporting parameter is set
	if getQueryParam(c, "export") == "true" {
		ts := time.Now().Format(time.DateOnly)

		filename := fmt.Sprintf("pgweb-dbstats-%s-%s.%s", connCtx.Database, ts, format)
		c.Writer.Header().Set("Content-disposition", "attachment;filename="+filename)
	}

	switch format {
	case "json":
		c.JSON(http.StatusOK, res)
	case "csv":
		c.Data(http.StatusOK, "text/csv", res.CSV())
	case "xml":
		c.XML(200, res)
	default:
		badRequest(c, "invalid format")
	}
}

// HandleQuery runs the database query
func HandleQuery(query string, c *gin.Context) {
	metrics.IncrementQueriesCount()

	rawQuery, err := base64.StdEncoding.DecodeString(desanitize64(query))
	if err == nil {
		query = string(rawQuery)
	}

	result, err := DB(c).Query(query)
	if err != nil {
		badRequest(c, err)
		return
	}

	format := getQueryParam(c, "format")
	filename := getQueryParam(c, "filename")

	if filename == "" {
		filename = fmt.Sprintf("pgweb-%v.%v", time.Now().Unix(), format)
	}

	if format != "" {
		c.Writer.Header().Set("Content-disposition", "attachment;filename="+filename)
	}

	switch format {
	case "csv":
		c.Data(200, "text/csv", result.CSV())
	case "json":
		c.Data(200, "application/json", result.JSON())
	case "xml":
		c.XML(200, result)
	default:
		c.JSON(200, result)
	}
}

// GetBookmarks renders the list of available bookmarks
func GetBookmarks(c *gin.Context) {
	manager := bookmarks.NewManager(command.Opts.BookmarksDir)
	ids, err := manager.ListIDs()
	serveResult(c, ids, err)
}

// GetInfo renders the pgweb system information
func GetInfo(c *gin.Context) {
	successResponse(c, gin.H{
		"app": command.Info,
		"features": gin.H{
			"session_lock":   command.Opts.LockSession,
			"query_timeout":  command.Opts.QueryTimeout,
			"local_queries":  QueryStore != nil,
			"bookmarks_only": command.Opts.BookmarksOnly,
		},
	})
}

// DataExport performs database table export
func DataExport(c *gin.Context) {
	db := DB(c)

	info, err := db.Info()
	if err != nil {
		badRequest(c, err)
		return
	}

	dump := client.Dump{
		Table: strings.TrimSpace(c.Request.FormValue("table")),
	}

	// Perform validation of pg_dump command availability and compatibility.
	// Must be done before the actual command is executed to display errors.
	if err := dump.Validate(db.ServerVersion()); err != nil {
		badRequest(c, err)
		return
	}

	formattedInfo := info.Format()[0]
	filename := formattedInfo["current_database"].(string)
	if dump.Table != "" {
		filename = filename + "_" + dump.Table
	}

	filename = sanitizeFilename(filename)
	filename = fmt.Sprintf("%s_%s", filename, time.Now().Format("20060102_150405"))

	c.Header(
		"Content-Disposition",
		fmt.Sprintf(`attachment; filename="%s.sql.gz"`, filename),
	)

	err = dump.Export(c.Request.Context(), db.ConnectionString, c.Writer)
	if err != nil {
		logger.WithError(err).Error("pg_dump command failed")
		badRequest(c, err)
	}
}

// GetFunction renders function information
func GetFunction(c *gin.Context) {
	res, err := DB(c).Function(c.Param("id"))
	serveResult(c, res, err)
}

func GetLocalQueries(c *gin.Context) {
	connCtx, err := DB(c).GetConnContext()
	if err != nil {
		badRequest(c, err)
		return
	}

	storeQueries, err := QueryStore.ReadAll()
	if err != nil {
		badRequest(c, err)
		return
	}

	queries := []localQuery{}
	for _, q := range storeQueries {
		if !q.IsPermitted(connCtx.Host, connCtx.User, connCtx.Database, connCtx.Mode) {
			continue
		}

		queries = append(queries, localQuery{
			ID:          q.ID,
			Title:       q.Meta.Title,
			Description: q.Meta.Description,
			Query:       cleanQuery(q.Data),
		})
	}

	successResponse(c, queries)
}

func RunLocalQuery(c *gin.Context) {
	query, err := QueryStore.Read(c.Param("id"))
	if err != nil {
		if err == queries.ErrQueryFileNotExist {
			query = nil
		} else {
			badRequest(c, err)
			return
		}
	}
	if query == nil {
		errorResponse(c, 404, "query not found")
		return
	}

	connCtx, err := DB(c).GetConnContext()
	if err != nil {
		badRequest(c, err)
		return
	}

	if !query.IsPermitted(connCtx.Host, connCtx.User, connCtx.Database, connCtx.Mode) {
		errorResponse(c, 404, "query not found")
		return
	}

	if c.Request.Method == http.MethodGet {
		successResponse(c, localQuery{
			ID:          query.ID,
			Title:       query.Meta.Title,
			Description: query.Meta.Description,
			Query:       query.Data,
		})
		return
	}

	statement := cleanQuery(query.Data)
	if statement == "" {
		badRequest(c, errQueryRequired)
		return
	}

	HandleQuery(statement, c)
}


================================================
FILE: pkg/api/api_test.go
================================================
package api

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

func Test_assetContentType(t *testing.T) {
	samples := map[string]string{
		"foo.html": "text/html; charset=utf-8",
		"foo.css":  "text/css; charset=utf-8",
		"foo.js":   "application/javascript",
		"foo.icon": "image-x-icon",
		"foo.png":  "image/png",
		"foo.jpg":  "image/jpeg",
		"foo.gif":  "image/gif",
		"foo.eot":  "application/vnd.ms-fontobject",
		"foo.svg":  "image/svg+xml",
		"foo.foo":  "text/plain; charset=utf-8",
		"foo":      "text/plain; charset=utf-8",
	}

	alternatives := map[string]string{
		"foo.js": "text/javascript; charset=utf-8",
	}

	for name, expected := range samples {
		if alternatives[name] == "" {
			assert.Equal(t, expected, assetContentType(name))
			continue
		}

		actual := assetContentType(name)

		if actual != expected && actual != alternatives[name] {
			t.Errorf("expected %v but got %v (alternative value failed)", expected, actual)
		}
	}
}


================================================
FILE: pkg/api/errors.go
================================================
package api

import (
	"errors"
)

var (
	errNotConnected         = errors.New("Not connected")
	errNotPermitted         = errors.New("Not permitted")
	errInvalidConnString    = errors.New("Invalid connection string")
	errSessionRequired      = errors.New("Session ID is required")
	errSessionLocked        = errors.New("Session is locked")
	errURLRequired          = errors.New("URL parameter is required")
	errQueryRequired        = errors.New("Query parameter is required")
	errDatabaseNameRequired = errors.New("Database name is required")
)


================================================
FILE: pkg/api/helpers.go
================================================
package api

import (
	"fmt"
	"mime"
	"net/http"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"

	"github.com/gin-gonic/gin"

	"github.com/sosedoff/pgweb/pkg/shared"
)

var (
	// Mime types definitions
	extraMimeTypes = map[string]string{
		".icon": "image-x-icon",
		".ttf":  "application/x-font-ttf",
		".woff": "application/x-font-woff",
		".eot":  "application/vnd.ms-fontobject",
		".svg":  "image/svg+xml",
		".html": "text/html; charset-utf-8",
	}

	// Paths that dont require database connection
	allowedPaths = map[string]bool{
		"/api/sessions":  true,
		"/api/info":      true,
		"/api/connect":   true,
		"/api/bookmarks": true,
		"/api/history":   true,
	}

	// List of characters replaced by javascript code to make queries url-safe.
	base64subs = map[string]string{
		"-": "+",
		"_": "/",
		".": "=",
	}

	// Regular expression to remove unwanted characters in filenames
	regexCleanFilename = regexp.MustCompile(`[^\w]+`)
)

type Error struct {
	Message string `json:"error"`
}

func NewError(err error) Error {
	return Error{err.Error()}
}

// Returns a clean query without any comment statements
func cleanQuery(query string) string {
	lines := []string{}

	for _, line := range strings.Split(query, "\n") {
		line = strings.TrimSpace(line)
		if strings.HasPrefix(line, "--") {
			continue
		}
		lines = append(lines, line)
	}

	return strings.TrimSpace(strings.Join(lines, "\n"))
}

func desanitize64(query string) string {
	// Before feeding the string into decoded, we must "reconstruct" the base64 data.
	// Javascript replaces a few characters to be url-safe.
	for olds, news := range base64subs {
		query = strings.Replace(query, olds, news, -1)
	}

	return query
}

func sanitizeFilename(str string) string {
	str = strings.ReplaceAll(str, ".", "_")
	return regexCleanFilename.ReplaceAllString(str, "")
}

func getSessionId(req *http.Request) string {
	id := req.Header.Get("x-session-id")
	if id == "" {
		id = req.URL.Query().Get("_session_id")
	}
	return id
}

func getQueryParam(c *gin.Context, name string) string {
	result := ""
	q := c.Request.URL.Query()

	if len(q[name]) > 0 {
		result = q[name][0]
	}

	return result
}

func parseIntFormValue(c *gin.Context, name string, defValue int) (int, error) {
	val := c.Request.FormValue(name)

	if val == "" {
		return defValue, nil
	}

	num, err := strconv.Atoi(val)
	if err != nil {
		return defValue, fmt.Errorf("%s must be a number", name)
	}

	if num < 1 && defValue != 0 {
		return defValue, fmt.Errorf("%s must be greater than 0", name)
	}

	return num, nil
}

func parseSshInfo(c *gin.Context) *shared.SSHInfo {
	info := shared.SSHInfo{
		Host:        c.Request.FormValue("ssh_host"),
		Port:        c.Request.FormValue("ssh_port"),
		User:        c.Request.FormValue("ssh_user"),
		Password:    c.Request.FormValue("ssh_password"),
		Key:         c.Request.FormValue("ssh_key"),
		KeyPassword: c.Request.FormValue("ssh_key_password"),
	}

	if info.Port == "" {
		info.Port = "22"
	}

	return &info
}

func assetContentType(name string) string {
	ext := filepath.Ext(name)
	result := mime.TypeByExtension(ext)

	if result == "" {
		result = extraMimeTypes[ext]
	}

	if result == "" {
		result = "text/plain; charset=utf-8"
	}

	return result
}

// Send a query result to client
func serveResult(c *gin.Context, result interface{}, err interface{}) {
	if err != nil {
		badRequest(c, err)
		return
	}

	successResponse(c, result)
}

// Send successful response back to client
func successResponse(c *gin.Context, data interface{}) {
	c.JSON(200, data)
}

// Send an error response back to client
func errorResponse(c *gin.Context, status int, err interface{}) {
	var message interface{}

	switch v := err.(type) {
	case error:
		message = v.Error()
	case string:
		message = v
	default:
		message = v
	}

	c.AbortWithStatusJSON(status, gin.H{"status": status, "error": message})
}

// Send a bad request (http 400) back to client
func badRequest(c *gin.Context, err interface{}) {
	errorResponse(c, 400, err)
}


================================================
FILE: pkg/api/helpers_test.go
================================================
package api

import (
	"errors"
	"net/http"
	"net/http/httptest"
	"net/url"
	"testing"

	"github.com/gin-gonic/gin"
	"github.com/stretchr/testify/assert"
)

func Test_desanitize64(t *testing.T) {
	examples := map[string]string{
		"test":        "test",
		"test+test+":  "test-test-",
		"test/test/":  "test_test_",
		"test=test==": "test.test..",
	}

	for expected, example := range examples {
		assert.Equal(t, expected, desanitize64(example))
	}
}

func Test_cleanQuery(t *testing.T) {
	assert.Equal(t, "a\nb\nc", cleanQuery("a\nb\nc"))
	assert.Equal(t, "", cleanQuery("--something"))
	assert.Equal(t, "test", cleanQuery("--test\ntest\n   -- test\n"))
}

func Test_sanitizeFilename(t *testing.T) {
	examples := map[string]string{
		"foo":              "foo",
		"fooBar":           "fooBar",
		"foo.bar":          "foo_bar",
		`"foo"."bar"`:      "foo_bar",
		"!@#$foo.&&*(&bar": "foo_bar",
	}

	for given, expected := range examples {
		t.Run(given, func(t *testing.T) {
			assert.Equal(t, expected, sanitizeFilename(given))
		})
	}
}

func Test_getSessionId(t *testing.T) {
	req := &http.Request{Header: http.Header{}}
	req.Header.Add("x-session-id", "token")
	assert.Equal(t, "token", getSessionId(req))

	req = &http.Request{}
	req.URL, _ = url.Parse("http://foobar/?_session_id=token")
	assert.Equal(t, "token", getSessionId(req))
}

func Test_serveResult(t *testing.T) {
	server := gin.Default()
	server.GET("/good", func(c *gin.Context) {
		serveResult(c, gin.H{"foo": "bar"}, nil)
	})
	server.GET("/bad", func(c *gin.Context) {
		serveResult(c, nil, errors.New("message"))
	})
	server.GET("/nodata", func(c *gin.Context) {
		serveResult(c, nil, nil)
	})

	w := httptest.NewRecorder()
	req, _ := http.NewRequest("GET", "/good", nil)
	server.ServeHTTP(w, req)
	assert.Equal(t, 200, w.Code)
	assert.Equal(t, `{"foo":"bar"}`, w.Body.String())

	w = httptest.NewRecorder()
	req, _ = http.NewRequest("GET", "/bad", nil)
	server.ServeHTTP(w, req)
	assert.Equal(t, 400, w.Code)
	assert.Equal(t, `{"error":"message","status":400}`, w.Body.String())

	w = httptest.NewRecorder()
	req, _ = http.NewRequest("GET", "/nodata", nil)
	server.ServeHTTP(w, req)
	assert.Equal(t, 200, w.Code)
	assert.Equal(t, `null`, w.Body.String())
}


================================================
FILE: pkg/api/logger.go
================================================
package api

import (
	"net/http"
	"regexp"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/sirupsen/logrus"

	"github.com/sosedoff/pgweb/pkg/command"
)

var (
	logger *logrus.Logger

	reConnectToken = regexp.MustCompile("/connect/(.*)")
)

func init() {
	if logger == nil {
		logger = logrus.New()
	}
}

// TODO: Move this into server struct when it's ready
func SetLogger(l *logrus.Logger) {
	logger = l
}

func RequestLogger(logger *logrus.Logger) gin.HandlerFunc {
	debug := logger.Level > logrus.InfoLevel
	logForwardedUser := command.Opts.LogForwardedUser

	return func(c *gin.Context) {
		start := time.Now()
		path := c.Request.URL.Path

		// Process request
		c.Next()

		if !debug {
			// Skip static assets logging
			if strings.Contains(path, "/static/") {
				return
			}

			path = sanitizeLogPath(path)
		}

		status := c.Writer.Status()
		end := time.Now()
		latency := end.Sub(start)

		fields := logrus.Fields{
			"status":      status,
			"method":      c.Request.Method,
			"remote_addr": c.ClientIP(),
			"duration":    latency.String(),
			"duration_ms": latency.Milliseconds(),
			"path":        path,
		}

		if reqID := getRequestID(c); reqID != "" {
			fields["id"] = reqID
		}

		if logForwardedUser {
			if forwardedUser := c.GetHeader("X-Forwarded-User"); forwardedUser != "" {
				fields["forwarded_user"] = forwardedUser
			}
			if forwardedEmail := c.GetHeader("X-Forwarded-Email"); forwardedEmail != "" {
				fields["forwarded_email"] = forwardedEmail
			}
		}

		if err := c.Errors.Last(); err != nil {
			fields["error"] = err.Error()
		}

		// Additional fields for debugging
		if debug {
			fields["raw_query"] = c.Request.URL.RawQuery

			if c.Request.Method != http.MethodGet {
				fields["raw_form"] = c.Request.Form
			}
		}

		entry := logger.WithFields(fields)
		msg := "http_request"

		switch {
		case status >= http.StatusBadRequest && status < http.StatusInternalServerError:
			entry.Warn(msg)
		case status >= http.StatusInternalServerError:
			entry.Error(msg)
		default:
			entry.Info(msg)
		}
	}
}

func sanitizeLogPath(str string) string {
	return reConnectToken.ReplaceAllString(str, "/connect/REDACTED")
}

func getRequestID(c *gin.Context) string {
	id := c.GetHeader("x-request-id")
	if id == "" {
		id = c.GetHeader("x-amzn-trace-id")
	}
	return id
}


================================================
FILE: pkg/api/logger_test.go
================================================
package api

import (
	"net/http"
	"testing"

	"github.com/gin-gonic/gin"
	"github.com/stretchr/testify/assert"
)

func Test_getRequestID(t *testing.T) {
	examples := []struct {
		headers map[string]string
		result  string
	}{
		{map[string]string{}, ""},
		{map[string]string{"X-Request-ID": "foo"}, "foo"},
		{map[string]string{"x-request-id": "foo"}, "foo"},
		{map[string]string{"x-request-id": "foo"}, "foo"},
		{map[string]string{"x-request-id": "foo", "x-amzn-trace-id": "amz"}, "foo"},
	}

	for _, ex := range examples {
		req := &http.Request{Header: http.Header{}}
		for k, v := range ex.headers {
			req.Header.Set(k, v)
		}

		assert.Equal(t, ex.result, getRequestID(&gin.Context{Request: req}))
	}
}


================================================
FILE: pkg/api/middleware.go
================================================
package api

import (
	"strings"

	"github.com/gin-gonic/gin"

	"github.com/sosedoff/pgweb/pkg/command"
)

// Middleware to check database connection status before running queries
func dbCheckMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		path := strings.Replace(c.Request.URL.Path, command.Opts.Prefix, "", -1)

		// Allow whitelisted paths
		if allowedPaths[path] {
			c.Next()
			return
		}

		// Check if session exists in single-session mode
		if !command.Opts.Sessions {
			if DbClient == nil {
				badRequest(c, errNotConnected)
				return
			}

			c.Next()
			return
		}

		// Determine session ID from the client request
		sid := getSessionId(c.Request)
		if sid == "" {
			badRequest(c, errSessionRequired)
			return
		}

		// Determine the database connection handle for the session
		conn := DbSessions.Get(sid)
		if conn == nil {
			badRequest(c, errNotConnected)
			return
		}

		c.Next()
	}
}

// Middleware to inject CORS headers
func corsMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		c.Header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
		c.Header("Access-Control-Expose-Headers", "*")
		c.Header("Access-Control-Allow-Origin", command.Opts.CorsOrigin)
	}
}

func requireLocalQueries() gin.HandlerFunc {
	return func(c *gin.Context) {
		if QueryStore == nil {
			badRequest(c, "local queries are disabled")
			return
		}

		c.Next()
	}
}


================================================
FILE: pkg/api/routes.go
================================================
package api

import (
	"github.com/gin-gonic/gin"

	"github.com/sosedoff/pgweb/pkg/command"
	"github.com/sosedoff/pgweb/pkg/metrics"
)

func SetupMiddlewares(group *gin.RouterGroup) {
	if command.Opts.Cors {
		group.Use(corsMiddleware())
	}

	group.Use(dbCheckMiddleware())
}

func SetupRoutes(router *gin.Engine) {
	root := router.Group(command.Opts.Prefix)

	root.GET("/", gin.WrapH(GetHome(command.Opts.Prefix)))
	root.GET("/static/*path", gin.WrapH(GetAssets(command.Opts.Prefix)))
	root.GET("/connect/:resource", ConnectWithBackend)

	api := root.Group("/api")
	SetupMiddlewares(api)

	if command.Opts.Sessions {
		api.GET("/sessions", GetSessions)
	}

	api.GET("/info", GetInfo)
	api.POST("/connect", Connect)
	api.POST("/disconnect", Disconnect)
	api.POST("/switchdb", SwitchDb)
	api.GET("/databases", GetDatabases)
	api.GET("/connection", GetConnectionInfo)
	api.GET("/server_settings", GetServerSettings)
	api.GET("/activity", GetActivity)
	api.GET("/schemas", GetSchemas)
	api.GET("/objects", GetObjects)
	api.GET("/tables/:table", GetTable)
	api.GET("/tables/:table/rows", GetTableRows)
	api.GET("/tables/:table/info", GetTableInfo)
	api.GET("/tables/:table/indexes", GetTableIndexes)
	api.GET("/tables/:table/constraints", GetTableConstraints)
	api.GET("/tables_stats", GetTablesStats)
	api.GET("/functions/:id", GetFunction)
	api.GET("/query", RunQuery)
	api.POST("/query", RunQuery)
	api.GET("/explain", ExplainQuery)
	api.POST("/explain", ExplainQuery)
	api.GET("/analyze", AnalyzeQuery)
	api.POST("/analyze", AnalyzeQuery)
	api.GET("/history", GetHistory)
	api.GET("/bookmarks", GetBookmarks)
	api.GET("/export", DataExport)
	api.GET("/local_queries", requireLocalQueries(), GetLocalQueries)
	api.GET("/local_queries/:id", requireLocalQueries(), RunLocalQuery)
	api.POST("/local_queries/:id", requireLocalQueries(), RunLocalQuery)
}

func SetupMetrics(engine *gin.Engine) {
	if command.Opts.MetricsEnabled && command.Opts.MetricsAddr == "" {
		// NOTE: We're not supporting the MetricsPath CLI option here to avoid the route conflicts.
		engine.GET("/metrics", gin.WrapH(metrics.NewHandler()))
	}
}


================================================
FILE: pkg/api/session_manager.go
================================================
package api

import (
	"sync"
	"time"

	"github.com/sirupsen/logrus"

	"github.com/sosedoff/pgweb/pkg/client"
	"github.com/sosedoff/pgweb/pkg/metrics"
)

type SessionManager struct {
	logger      *logrus.Logger
	sessions    map[string]*client.Client
	mu          sync.Mutex
	idleTimeout time.Duration
}

func NewSessionManager(logger *logrus.Logger) *SessionManager {
	return &SessionManager{
		logger:   logger,
		sessions: map[string]*client.Client{},
		mu:       sync.Mutex{},
	}
}

func (m *SessionManager) SetIdleTimeout(timeout time.Duration) {
	m.idleTimeout = timeout
}

func (m *SessionManager) IDs() []string {
	m.mu.Lock()
	defer m.mu.Unlock()

	ids := []string{}
	for k := range m.sessions {
		ids = append(ids, k)
	}

	return ids
}

func (m *SessionManager) Sessions() map[string]*client.Client {
	m.mu.Lock()
	defer m.mu.Unlock()

	sessions := make(map[string]*client.Client, len(m.sessions))
	for k, v := range m.sessions {
		sessions[k] = v
	}

	return sessions
}

func (m *SessionManager) Get(id string) *client.Client {
	m.mu.Lock()
	defer m.mu.Unlock()

	return m.sessions[id]
}

func (m *SessionManager) Add(id string, conn *client.Client) {
	m.mu.Lock()
	defer m.mu.Unlock()

	m.sessions[id] = conn
	metrics.SetSessionsCount(len(m.sessions))
}

func (m *SessionManager) Remove(id string) bool {
	m.mu.Lock()
	defer m.mu.Unlock()

	conn, ok := m.sessions[id]
	if ok {
		conn.Close()
		delete(m.sessions, id)
	}

	metrics.SetSessionsCount(len(m.sessions))
	return ok
}

func (m *SessionManager) Len() int {
	m.mu.Lock()
	defer m.mu.Unlock()

	return len(m.sessions)
}

func (m *SessionManager) Cleanup() int {
	if m.idleTimeout == 0 {
		return 0
	}

	removed := 0

	m.logger.Debug("starting idle sessions cleanup")
	defer func() {
		m.logger.Debug("removed idle sessions:", removed)
	}()

	for _, id := range m.staleSessions() {
		m.logger.WithField("id", id).Debug("closing stale session")
		if m.Remove(id) {
			removed++
		}
	}

	return removed
}

func (m *SessionManager) RunPeriodicCleanup() {
	m.logger.WithField("timeout", m.idleTimeout).Info("session manager cleanup enabled")

	for range time.Tick(time.Minute) {
		m.Cleanup()
	}
}

func (m *SessionManager) staleSessions() []string {
	m.mu.Lock()
	defer m.mu.Unlock()

	now := time.Now()
	ids := []string{}

	for id, conn := range m.sessions {
		if now.Sub(conn.LastQueryTime()) > m.idleTimeout {
			ids = append(ids, id)
		}
	}

	return ids
}


================================================
FILE: pkg/api/session_manager_test.go
================================================
package api

import (
	"sort"
	"testing"
	"time"

	"github.com/sirupsen/logrus"
	"github.com/stretchr/testify/assert"

	"github.com/sosedoff/pgweb/pkg/client"
)

func TestSessionManager(t *testing.T) {
	t.Run("return ids", func(t *testing.T) {
		manager := NewSessionManager(nil)
		assert.Equal(t, []string{}, manager.IDs())

		manager.sessions["foo"] = &client.Client{}
		manager.sessions["bar"] = &client.Client{}

		ids := manager.IDs()
		sort.Strings(ids)
		assert.Equal(t, []string{"bar", "foo"}, ids)
	})

	t.Run("get session", func(t *testing.T) {
		manager := NewSessionManager(nil)
		assert.Nil(t, manager.Get("foo"))

		manager.sessions["foo"] = &client.Client{}
		assert.NotNil(t, manager.Get("foo"))
	})

	t.Run("set session", func(t *testing.T) {
		manager := NewSessionManager(nil)
		assert.Nil(t, manager.Get("foo"))

		manager.Add("foo", &client.Client{})
		assert.NotNil(t, manager.Get("foo"))
	})

	t.Run("remove session", func(t *testing.T) {
		manager := NewSessionManager(nil)
		assert.Nil(t, manager.Get("foo"))

		manager.Add("foo", &client.Client{})
		assert.NotNil(t, manager.Get("foo"))
		assert.True(t, manager.Remove("foo"))
		assert.False(t, manager.Remove("foo"))
		assert.Nil(t, manager.Get("foo"))
	})

	t.Run("return len", func(t *testing.T) {
		manager := NewSessionManager(nil)
		manager.sessions["foo"] = &client.Client{}
		manager.sessions["bar"] = &client.Client{}

		assert.Equal(t, 2, manager.Len())
	})

	t.Run("clean up stale sessions", func(t *testing.T) {
		manager := NewSessionManager(logrus.New())
		conn := &client.Client{}
		manager.Add("foo", conn)

		assert.Equal(t, 1, manager.Len())
		assert.Equal(t, 0, manager.Cleanup())
		assert.Equal(t, 1, manager.Len())

		res, err := conn.Query("select 1")
		assert.Nil(t, res)
		assert.Nil(t, err)

		manager.SetIdleTimeout(time.Minute)
		assert.Equal(t, 1, manager.Cleanup())
		assert.Equal(t, 0, manager.Len())
		assert.True(t, conn.IsClosed())
	})
}


================================================
FILE: pkg/api/types.go
================================================
package api

type localQuery struct {
	ID          string `json:"id"`
	Title       string `json:"title,omitempty"`
	Description string `json:"description,omitempty"`
	Query       string `json:"query"`
}


================================================
FILE: pkg/bookmarks/bookmarks.go
================================================
package bookmarks

import (
	"os"

	"github.com/sosedoff/pgweb/pkg/command"
	"github.com/sosedoff/pgweb/pkg/shared"
)

// Bookmark contains information about bookmarked database connection
type Bookmark struct {
	ID          string          // ID generated from the filename
	URL         string          // Postgres connection URL
	Host        string          // Server hostname
	Port        int             // Server port
	User        string          // Database user
	UserVar     string          // Database user environment variable
	Password    string          // User password
	PasswordVar string          // User password environment variable
	Database    string          // Database name
	SSLMode     string          // Connection SSL mode
	SSH         *shared.SSHInfo // SSH tunnel config
	ReadOnly    bool            // Enable read-only transaction mode
}

// SSHInfoIsEmpty returns true if ssh configuration is not provided
func (b Bookmark) SSHInfoIsEmpty() bool {
	return b.SSH == nil || (b.SSH.User == "" && b.SSH.Host == "" && b.SSH.Port == "")
}

// ConvertToOptions returns an options struct from connection details
func (b Bookmark) ConvertToOptions() command.Options {
	user := b.User
	if b.User == "" {
		user = os.Getenv(b.UserVar)
	}

	pass := b.Password
	if b.Password == "" {
		pass = os.Getenv(b.PasswordVar)
	}

	return command.Options{
		URL:      b.URL,
		Host:     b.Host,
		Port:     b.Port,
		User:     user,
		Pass:     pass,
		DbName:   b.Database,
		SSLMode:  b.SSLMode,
		ReadOnly: b.ReadOnly,
	}
}


================================================
FILE: pkg/bookmarks/bookmarks_test.go
================================================
package bookmarks

import (
	"testing"

	"github.com/sosedoff/pgweb/pkg/command"
	"github.com/sosedoff/pgweb/pkg/shared"
	"github.com/stretchr/testify/assert"
)

func TestBookmarkSSHInfoIsEmpty(t *testing.T) {
	t.Run("empty", func(t *testing.T) {
		info := &shared.SSHInfo{
			Host: "",
			Port: "",
			User: "",
		}

		b := Bookmark{SSH: nil}
		assert.True(t, b.SSHInfoIsEmpty())

		b = Bookmark{SSH: info}
		assert.True(t, b.SSHInfoIsEmpty())
	})

	t.Run("only host set", func(t *testing.T) {
		b := Bookmark{SSH: &shared.SSHInfo{Host: "localhost"}}
		assert.False(t, b.SSHInfoIsEmpty())
	})

	t.Run("only port set", func(t *testing.T) {
		b := Bookmark{SSH: &shared.SSHInfo{Port: "8080"}}
		assert.False(t, b.SSHInfoIsEmpty())
	})

	t.Run("only user set", func(t *testing.T) {
		b := Bookmark{SSH: &shared.SSHInfo{User: "postgres"}}
		assert.False(t, b.SSHInfoIsEmpty())
	})

	t.Run("populated", func(t *testing.T) {
		info := &shared.SSHInfo{
			Host: "localhost",
			Port: "8080",
			User: "postgres",
		}

		b := Bookmark{SSH: info}
		assert.False(t, b.SSHInfoIsEmpty())
	})
}

func TestBookmarkWithVarsConvertToOptions(t *testing.T) {
	t.Run("literals set", func(t *testing.T) {
		b := Bookmark{
			User:        "user",
			UserVar:     "",
			Password:    "password",
			PasswordVar: "",
		}

		expOpt := command.Options{
			User: "user",
			Pass: "password",
		}

		opt := b.ConvertToOptions()
		assert.Equal(t, expOpt, opt)
	})

	t.Run("all set", func(t *testing.T) {
		b := Bookmark{
			User:        "user",
			UserVar:     "DB_USER",
			Password:    "password",
			PasswordVar: "DB_PASSWORD",
		}

		expOpt := command.Options{
			User: "user",
			Pass: "password",
		}

		t.Setenv("DB_USER", "user123")
		t.Setenv("DB_PASSWORD", "password123")

		opt := b.ConvertToOptions()
		assert.Equal(t, expOpt, opt)
	})

	t.Run("env vars set", func(t *testing.T) {
		b := Bookmark{
			User:        "",
			UserVar:     "DB_USER",
			Password:    "",
			PasswordVar: "DB_PASSWORD",
		}

		expOpt := command.Options{
			User: "user123",
			Pass: "password123",
		}

		t.Setenv("DB_USER", "user123")
		t.Setenv("DB_PASSWORD", "password123")

		opt := b.ConvertToOptions()
		assert.Equal(t, expOpt, opt)
	})
}

func TestBookmarkConvertToOptions(t *testing.T) {
	b := Bookmark{
		URL:      "postgres://username:password@host:port/database?sslmode=disable",
		Host:     "localhost",
		Port:     5432,
		User:     "postgres",
		Password: "password",
		Database: "mydatabase",
		SSLMode:  "disable",
		ReadOnly: true,
	}

	expOpt := command.Options{
		URL:      "postgres://username:password@host:port/database?sslmode=disable",
		Host:     "localhost",
		Port:     5432,
		User:     "postgres",
		Pass:     "password",
		DbName:   "mydatabase",
		SSLMode:  "disable",
		ReadOnly: true,
	}

	opt := b.ConvertToOptions()
	assert.Equal(t, expOpt, opt)
}


================================================
FILE: pkg/bookmarks/manager.go
================================================
package bookmarks

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"github.com/BurntSushi/toml"
)

type Manager struct {
	dir string
}

func NewManager(dir string) Manager {
	return Manager{
		dir: dir,
	}
}

func (m Manager) Get(id string) (*Bookmark, error) {
	bookmarks, err := m.list()
	if err != nil {
		return nil, err
	}

	for _, b := range bookmarks {
		if b.ID == id {
			return &b, nil
		}
	}

	return nil, fmt.Errorf("bookmark %v not found", id)
}

func (m Manager) List() ([]Bookmark, error) {
	return m.list()
}

func (m Manager) ListIDs() ([]string, error) {
	bookmarks, err := m.list()
	if err != nil {
		return nil, err
	}

	ids := make([]string, len(bookmarks))
	for i, bookmark := range bookmarks {
		ids[i] = bookmark.ID
	}

	return ids, nil
}

func (m Manager) list() ([]Bookmark, error) {
	result := []Bookmark{}

	if m.dir == "" {
		return result, nil
	}

	info, err := os.Stat(m.dir)
	if err != nil {
		// Do not fail if base dir does not exists: it's not created by default
		if errors.Is(err, os.ErrNotExist) {
			fmt.Fprintf(os.Stderr, "[WARN] bookmarks dir %s does not exist\n", m.dir)
			return result, nil
		}
		return nil, err
	}
	if !info.IsDir() {
		return nil, fmt.Errorf("path %s is not a directory", m.dir)
	}

	dirEntries, err := os.ReadDir(m.dir)
	if err != nil {
		return nil, err
	}

	for _, entry := range dirEntries {
		name := entry.Name()
		if filepath.Ext(name) != ".toml" {
			continue
		}

		bookmark, err := readBookmark(filepath.Join(m.dir, name))
		if err != nil {
			// Do not fail if one of the bookmarks is invalid
			fmt.Fprintf(os.Stderr, "[WARN] bookmark file %s is invalid: %s\n", name, err)
			continue
		}

		result = append(result, bookmark)
	}

	return result, nil
}

func readBookmark(path string) (Bookmark, error) {
	bookmark := Bookmark{
		ID: fileBasename(path),
	}

	_, err := os.Stat(path)
	if err != nil {
		if errors.Is(err, os.ErrNotExist) {
			err = fmt.Errorf("bookmark file %s does not exist", path)
		}
		return bookmark, err
	}

	buff, err := os.ReadFile(path)
	if err != nil {
		return bookmark, err
	}

	_, err = toml.Decode(string(buff), &bookmark)

	if bookmark.Port == 0 {
		bookmark.Port = 5432
	}

	// List of all supported postgres modes
	modes := []string{"disable", "allow", "prefer", "require", "verify-ca", "verify-full"}
	valid := false

	for _, mode := range modes {
		if bookmark.SSLMode == mode {
			valid = true
			break
		}
	}

	// Fall back to a default mode if mode is not set or invalid
	// Typical typo: ssl mode set to "disabled"
	if bookmark.SSLMode == "" || !valid {
		bookmark.SSLMode = "disable"
	}

	// Set default SSH port if it's not provided by user
	if bookmark.SSH != nil && bookmark.SSH.Port == "" {
		bookmark.SSH.Port = "22"
	}

	return bookmark, err
}

func fileBasename(path string) string {
	filename := filepath.Base(path)
	return strings.Replace(filename, filepath.Ext(path), "", 1)
}


================================================
FILE: pkg/bookmarks/manager_test.go
================================================
package bookmarks

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestManagerList(t *testing.T) {
	examples := []struct {
		dir string
		num int
		err string
	}{
		{"../../data", 4, ""},
		{"../../data/bookmark.toml", 0, "is not a directory"},
		{"../../data2", 0, ""},
		{"", 0, ""},
	}

	for _, ex := range examples {
		t.Run(ex.dir, func(t *testing.T) {
			bookmarks, err := NewManager(ex.dir).List()
			if ex.err != "" {
				assert.Contains(t, err.Error(), ex.err)
			}
			assert.Len(t, bookmarks, ex.num)
		})
	}
}

func TestManagerListIDs(t *testing.T) {
	ids, err := NewManager("../../data").ListIDs()
	assert.NoError(t, err)
	assert.Equal(t, []string{
		"bookmark",
		"bookmark_invalid_ssl",
		"bookmark_url",
		"bookmark_with_ssh",
	}, ids)
}

func TestManagerGet(t *testing.T) {
	manager := NewManager("../../data")

	b, err := manager.Get("bookmark")
	assert.NoError(t, err)
	assert.Equal(t, "bookmark", b.ID)

	b, err = manager.Get("foo")
	assert.Equal(t, "bookmark foo not found", err.Error())
	assert.Nil(t, b)
}

func Test_fileBasename(t *testing.T) {
	assert.Equal(t, "filename", fileBasename("filename.toml"))
	assert.Equal(t, "filename", fileBasename("path/filename.toml"))
	assert.Equal(t, "filename", fileBasename("~/long/path/filename.toml"))
	assert.Equal(t, "filename", fileBasename("filename"))
}

func Test_readBookmark(t *testing.T) {
	t.Run("good", func(t *testing.T) {
		b, err := readBookmark("../../data/bookmark.toml")
		assert.NoError(t, err)
		assert.Equal(t, "bookmark", b.ID)
		assert.Equal(t, "localhost", b.Host)
		assert.Equal(t, 5432, b.Port)
		assert.Equal(t, "postgres", b.User)
		assert.Equal(t, "mydatabase", b.Database)
		assert.Equal(t, "disable", b.SSLMode)
		assert.Equal(t, "", b.Password)
		assert.Equal(t, "", b.URL)
	})

	t.Run("with url", func(t *testing.T) {
		b, err := readBookmark("../../data/bookmark_url.toml")
		assert.NoError(t, err)
		assert.Equal(t, "postgres://username:password@host:port/database?sslmode=disable", b.URL)
		assert.Equal(t, "", b.Host)
		assert.Equal(t, 5432, b.Port)
		assert.Equal(t, "", b.User)
		assert.Equal(t, "", b.Database)
		assert.Equal(t, "disable", b.SSLMode)
		assert.Equal(t, "", b.Password)
	})

	t.Run("with ssh options", func(t *testing.T) {
		b, err := readBookmark("../../data/bookmark_with_ssh.toml")
		assert.NoError(t, err)
		assert.NotNil(t, b.SSH)

		sshc := b.SSH
		assert.Equal(t, "ssh-host", sshc.Host)
		assert.Equal(t, "ssh-user", sshc.User)
		assert.Equal(t, "ssh-password", sshc.Password)
		assert.Equal(t, "/path/to/key-file", sshc.Key)
		assert.Equal(t, "key-file-password", sshc.KeyPassword)
	})

	t.Run("invalid ssl", func(t *testing.T) {
		b, err := readBookmark("../../data/bookmark_invalid_ssl.toml")
		assert.NoError(t, err)
		assert.Equal(t, "disable", b.SSLMode)
	})

	t.Run("invalid file", func(t *testing.T) {
		_, err := readBookmark("foobar")
		assert.Equal(t, "bookmark file foobar does not exist", err.Error())
	})

	t.Run("invalid syntax", func(t *testing.T) {
		_, err := readBookmark("../../data/invalid.toml")
		assert.Equal(t, "toml: line 1: expected '.' or '=', but got 'e' instead", err.Error())
	})
}


================================================
FILE: pkg/cli/cli.go
================================================
package cli

import (
	"errors"
	"fmt"
	"os"
	"os/exec"
	"os/signal"
	"strings"
	"syscall"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/jessevdk/go-flags"
	"github.com/sirupsen/logrus"

	"github.com/sosedoff/pgweb/pkg/api"
	"github.com/sosedoff/pgweb/pkg/bookmarks"
	"github.com/sosedoff/pgweb/pkg/client"
	"github.com/sosedoff/pgweb/pkg/command"
	"github.com/sosedoff/pgweb/pkg/connection"
	"github.com/sosedoff/pgweb/pkg/metrics"
	"github.com/sosedoff/pgweb/pkg/queries"
	"github.com/sosedoff/pgweb/pkg/util"
)

var (
	logger  *logrus.Logger
	options command.Options

	readonlyWarning = `
--------------------------------------------------------------------------------
SECURITY WARNING: You are running Pgweb in read-only mode.
This mode is designed for environments where users could potentially delete or change data.
For proper read-only access please follow PostgreSQL role management documentation.
--------------------------------------------------------------------------------`
)

func init() {
	logger = logrus.New()
}

func exitWithMessage(message string) {
	fmt.Println("Error:", message)
	os.Exit(1)
}

func initClientUsingBookmark(baseDir, bookmarkName string) (*client.Client, error) {
	manager := bookmarks.NewManager(baseDir)
	bookmark, err := manager.Get(bookmarkName)
	if err != nil {
		return nil, err
	}

	return client.NewFromBookmark(bookmark)
}

func initClient() {
	if connection.IsBlank(command.Opts) && options.Bookmark == "" {
		return
	}

	var cl *client.Client
	var err error

	if options.Bookmark != "" {
		cl, err = initClientUsingBookmark(options.BookmarksDir, options.Bookmark)
	} else {
		cl, err = client.New()
	}
	if err != nil {
		exitWithMessage(err.Error())
	}

	if command.Opts.Debug {
		fmt.Println("Opening database connection using string:", cl.ConnectionString)
	}

	retryCount := command.Opts.RetryCount
	retryDelay := time.Second * time.Duration(command.Opts.RetryDelay)

	fmt.Println("Connecting to server...")
	abort, err := testClient(cl, int(retryCount), retryDelay)
	if err != nil {
		if abort {
			exitWithMessage(err.Error())
		} else {
			return
		}
	}

	if !command.Opts.Sessions {
		fmt.Printf("Connected to %s\n", cl.ServerVersionInfo())
	}

	fmt.Println("Checking database objects...")
	_, err = cl.Objects()
	if err != nil {
		exitWithMessage(err.Error())
	}

	api.DbClient = cl
}

func initOptions() {
	opts, err := command.ParseOptions(os.Args)
	if err != nil {
		switch errVal := err.(type) {
		case *flags.Error:
			if errVal.Type == flags.ErrHelp {
				fmt.Println("Available environment variables:")
				fmt.Println(command.AvailableEnvVars())
			}
			// no need to print error, flags package already does that
		default:
			fmt.Println(err.Error())
		}
		os.Exit(1)
	}
	command.Opts = opts
	options = opts

	if options.Version {
		printVersion()
		os.Exit(0)
	}

	if err := configureLogger(opts); err != nil {
		exitWithMessage(err.Error())
		return
	}

	if options.ReadOnly {
		fmt.Println(readonlyWarning)
	}

	if options.BinaryCodec != "" {
		if err := client.SetBinaryCodec(options.BinaryCodec); err != nil {
			exitWithMessage(err.Error())
		}
	}

	configureLocalQueryStore()
	printVersion()
}

func configureLocalQueryStore() {
	if options.Sessions || options.QueriesDir == "" {
		return
	}

	stat, err := os.Stat(options.QueriesDir)
	if err != nil {
		if errors.Is(err, os.ErrNotExist) {
			logger.Debugf("local queries directory %q does not exist, disabling feature", options.QueriesDir)
		} else {
			logger.Debugf("local queries feature disabled due to error: %v", err)
		}
		return
	}

	if !stat.IsDir() {
		logger.Debugf("local queries path %q is not a directory", options.QueriesDir)
		return
	}

	api.QueryStore = queries.NewStore(options.QueriesDir)
}

func configureLogger(opts command.Options) error {
	if options.Debug {
		logger.SetLevel(logrus.DebugLevel)
	} else {
		lvl, err := logrus.ParseLevel(opts.LogLevel)
		if err != nil {
			return err
		}
		logger.SetLevel(lvl)
	}

	switch options.LogFormat {
	case "text":
		logger.SetFormatter(&logrus.TextFormatter{})
	case "json":
		logger.SetFormatter(&logrus.JSONFormatter{})
	default:
		return fmt.Errorf("invalid logger format: %v", options.LogFormat)
	}

	return nil
}

func printVersion() {
	fmt.Println(command.VersionString())
}

func startServer() {
	router := gin.New()
	router.Use(api.RequestLogger(logger))
	router.Use(gin.Recovery())

	// Enable HTTP basic authentication only if both user and password are set
	if options.AuthUser != "" && options.AuthPass != "" {
		auth := map[string]string{options.AuthUser: options.AuthPass}
		router.Use(gin.BasicAuth(auth))
	}

	api.SetLogger(logger)
	api.SetupRoutes(router)
	api.SetupMetrics(router)

	fmt.Println("Starting server...")
	go func() {
		metrics.SetHealthy(true)

		err := router.Run(fmt.Sprintf("%v:%v", options.HTTPHost, options.HTTPPort))
		if err != nil {
			fmt.Println("Can't start server:", err)
			if strings.Contains(err.Error(), "address already in use") {
				openPage()
			}
			os.Exit(1)
		}
	}()
}

func startMetricsServer() {
	serverAddr := fmt.Sprintf("%v:%v", command.Opts.HTTPHost, command.Opts.HTTPPort)
	if options.MetricsAddr == serverAddr {
		return
	}

	err := metrics.StartServer(logger, options.MetricsPath, options.MetricsAddr)
	if err != nil {
		logger.WithError(err).Fatal("unable to start prometheus metrics server")
	}
}

func handleSignals() {
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt, syscall.SIGTERM)
	<-c
}

func openPage() {
	url := fmt.Sprintf("http://%v:%v/%s", options.HTTPHost, options.HTTPPort, options.Prefix)
	fmt.Println("To view database open", url, "in browser")

	if options.SkipOpen {
		return
	}

	_, err := exec.Command("which", "open").Output()
	if err != nil {
		return
	}

	_, err = exec.Command("open", url).Output()
	if err != nil {
		fmt.Println("Unable to auto-open pgweb URL:", err)
	}
}

// testClient attempts to establish a database connection until it succeeds or
// give up after certain number of retries. Retries only available when database
// name or a connection string is provided.
func testClient(cl *client.Client, retryCount int, retryDelay time.Duration) (abort bool, err error) {
	usingDefaultDB := command.Opts.DbName == "" && command.Opts.URL == ""

	for {
		err = cl.Test()
		if err == nil {
			return false, nil
		}

		// Continue normal start up if can't connect locally without database details.
		if usingDefaultDB {
			if errors.Is(err, client.ErrConnectionRefused) ||
				errors.Is(err, client.ErrAuthFailed) ||
				errors.Is(err, client.ErrDatabaseNotExist) {
				return false, err
			}
		}

		// Only retry if can't establish connection to the server.
		if errors.Is(err, client.ErrConnectionRefused) && retryCount > 0 {
			fmt.Printf("Connection error: %v, retrying in %v (%d remaining)\n", err, retryDelay, retryCount)
			retryCount--
			<-time.After(retryDelay)
			continue
		}

		return true, err
	}
}

func Run() {
	initOptions()
	initClient()

	if api.DbClient != nil {
		defer api.DbClient.Close()
	}

	if !options.Debug {
		gin.SetMode("release")
	}

	// Print memory usage every 30 seconds with debug flag
	if options.Debug {
		util.StartProfiler()
	}

	// Start session cleanup worker
	if options.Sessions {
		api.DbSessions = api.NewSessionManager(logger)

		if !command.Opts.DisableConnectionIdleTimeout {
			api.DbSessions.SetIdleTimeout(time.Minute * time.Duration(command.Opts.ConnectionIdleTimeout))
			go api.DbSessions.RunPeriodicCleanup()
		}
	}

	// Start a separate metrics http server. If metrics addr is not provided, we
	// add the metrics endpoint in the existing application server (see api.go).
	if options.MetricsEnabled && options.MetricsAddr != "" {
		go startMetricsServer()
	}

	startServer()
	openPage()
	handleSignals()
}


================================================
FILE: pkg/client/client.go
================================================
package client

import (
	"context"
	"errors"
	"fmt"
	"log"
	neturl "net/url"
	"reflect"
	"regexp"
	"strings"
	"time"

	"github.com/jmoiron/sqlx"
	_ "github.com/lib/pq"

	"github.com/sosedoff/pgweb/pkg/bookmarks"
	"github.com/sosedoff/pgweb/pkg/command"
	"github.com/sosedoff/pgweb/pkg/connection"
	"github.com/sosedoff/pgweb/pkg/history"
	"github.com/sosedoff/pgweb/pkg/shared"
	"github.com/sosedoff/pgweb/pkg/statements"
)

var (
	regexErrAuthFailed        = regexp.MustCompile(`(authentication failed|role "(.*)" does not exist)`)
	regexErrConnectionRefused = regexp.MustCompile(`(connection|actively) refused`)
	regexErrDatabaseNotExist  = regexp.MustCompile(`database "(.*)" does not exist`)
)

var (
	ErrAuthFailed        = errors.New("authentication failed")
	ErrConnectionRefused = errors.New("connection refused")
	ErrDatabaseNotExist  = errors.New("database does not exist")
)

type Client struct {
	db               *sqlx.DB
	tunnel           *Tunnel
	serverVersion    string
	serverType       string
	lastQueryTime    time.Time
	queryTimeout     time.Duration
	readonly         bool
	closed           bool
	External         bool             `json:"external"`
	History          []history.Record `json:"history"`
	ConnectionString string           `json:"connection_string"`
}

func getSchemaAndTable(str string) (string, string) {
	chunks := strings.Split(str, ".")
	if len(chunks) == 1 {
		return "public", chunks[0]
	}
	return chunks[0], chunks[1]
}

func New() (*Client, error) {
	str, err := connection.BuildStringFromOptions(command.Opts)

	if command.Opts.Debug && str != "" {
		fmt.Println("Creating a new client for:", str)
	}

	if err != nil {
		return nil, err
	}

	db, err := sqlx.Open("postgres", str)
	if err != nil {
		return nil, err
	}

	client := Client{
		db:               db,
		ConnectionString: str,
		History:          history.New(),
	}

	client.init()
	return &client, nil
}

func NewFromUrl(url string, sshInfo *shared.SSHInfo) (*Client, error) {
	var (
		tunnel *Tunnel
		err    error
	)

	if sshInfo != nil {
		if command.Opts.DisableSSH {
			return nil, fmt.Errorf("ssh connections are disabled")
		}
		if command.Opts.Debug {
			fmt.Println("Opening SSH tunnel for:", sshInfo)
		}

		tunnel, err = NewTunnel(sshInfo, url)
		if err != nil {
			tunnel.Close()
			return nil, err
		}

		err = tunnel.Configure()
		if err != nil {
			tunnel.Close()
			return nil, err
		}

		go tunnel.Start()

		uri, err := neturl.Parse(url)
		if err != nil {
			tunnel.Close()
			return nil, err
		}

		// Override remote postgres port with local proxy port
		url = strings.Replace(url, uri.Host, fmt.Sprintf("127.0.0.1:%v", tunnel.Port), 1)
	}

	if command.Opts.Debug {
		fmt.Println("Creating a new client for:", url)
	}

	uri, err := neturl.Parse(url)
	if err == nil && uri.Path == "" {
		return nil, fmt.Errorf("Database name is not provided")
	}

	db, err := sqlx.Open("postgres", url)
	if err != nil {
		return nil, err
	}

	client := Client{
		db:               db,
		tunnel:           tunnel,
		serverType:       postgresType,
		ConnectionString: url,
		History:          history.New(),
	}

	client.init()
	return &client, nil
}

func NewFromBookmark(bookmark *bookmarks.Bookmark) (*Client, error) {
	var (
		connStr string
		err     error
	)

	options := bookmark.ConvertToOptions()
	if options.URL != "" {
		connStr = options.URL
	} else {
		connStr, err = connection.BuildStringFromOptions(options)
		if err != nil {
			return nil, err
		}
	}

	var sshInfo *shared.SSHInfo
	if !bookmark.SSHInfoIsEmpty() {
		sshInfo = bookmark.SSH
	}

	client, err := NewFromUrl(connStr, sshInfo)
	if err != nil {
		return nil, err
	}

	if bookmark.ReadOnly {
		client.readonly = true
	}

	return client, nil
}

func (client *Client) init() {
	if command.Opts.QueryTimeout > 0 {
		client.queryTimeout = time.Second * time.Duration(command.Opts.QueryTimeout)
	}

	client.setServerVersion()
}

func (client *Client) setServerVersion() {
	res, err := client.query("SELECT version()")
	if err != nil || len(res.Rows) < 1 {
		return
	}

	version := res.Rows[0][0].(string)
	match, serverType, serverVersion := detectServerTypeAndVersion(version)
	if match {
		client.serverType = serverType
		client.serverVersion = serverVersion
	}
}

func (client *Client) Test() error {
	// NOTE: This is a different timeout defined in CLI OpenTimeout
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	err := client.db.PingContext(ctx)
	if err == nil {
		return nil
	}

	errMsg := err.Error()

	if regexErrConnectionRefused.MatchString(errMsg) {
		return ErrConnectionRefused
	}
	if regexErrAuthFailed.MatchString(errMsg) {
		return ErrAuthFailed
	}
	if regexErrDatabaseNotExist.MatchString(errMsg) {
		return ErrDatabaseNotExist
	}

	return err
}

func (client *Client) TestWithTimeout(timeout time.Duration) (result error) {
	ctx, cancel := context.WithTimeout(context.Background(), timeout)
	defer cancel()

	// Check connection status right away without waiting for the ticker to kick in.
	// We're expecting to get "connection refused" here for the most part.
	if err := client.db.PingContext(ctx); err == nil {
		return nil
	}

	ticker := time.NewTicker(250 * time.Millisecond)
	defer ticker.Stop()

	for {
		select {
		case <-ticker.C:
			result = client.db.PingContext(ctx)
			if result == nil {
				return
			}
		case <-ctx.Done():
			return
		}
	}
}

func (client *Client) Info() (*Result, error) {
	result, err := client.query(statements.Info)
	if err != nil {
		msg := err.Error()
		if strings.Contains(msg, "inet_") && (strings.Contains(msg, "not supported") || strings.Contains(msg, "permission denied")) {
			// Fetch client information without inet_ function calls
			result, err = client.query(statements.InfoSimple)
		}
	}
	return result, err
}

func (client *Client) Databases() ([]string, error) {
	return client.fetchRows(statements.Databases)
}

func (client *Client) Schemas() ([]string, error) {
	return client.fetchRows(statements.Schemas)
}

func (client *Client) Objects() (*Result, error) {
	return client.query(statements.Objects)
}

func (client *Client) Table(table string) (*Result, error) {
	schema, table := getSchemaAndTable(table)
	return client.query(statements.TableSchema, schema, table)
}

func (client *Client) MaterializedView(name string) (*Result, error) {
	return client.query(statements.MaterializedView, name)
}

func (client *Client) Function(id string) (*Result, error) {
	return client.query(statements.Function, id)
}

func (client *Client) TableRows(table string, opts RowsOptions) (*Result, error) {
	schema, table := getSchemaAndTable(table)
	sql := fmt.Sprintf(`SELECT * FROM "%s"."%s"`, schema, table)

	if opts.Where != "" {
		sql += fmt.Sprintf(" WHERE %s", opts.Where)
	}

	if opts.SortColumn != "" {
		if opts.SortOrder == "" {
			opts.SortOrder = "ASC"
		}

		sql += fmt.Sprintf(` ORDER BY "%s" %s`, opts.SortColumn, opts.SortOrder)
	}

	if opts.Limit > 0 {
		sql += fmt.Sprintf(" LIMIT %d", opts.Limit)
	}

	if opts.Offset > 0 {
		sql += fmt.Sprintf(" OFFSET %d", opts.Offset)
	}

	return client.query(sql)
}

func (client *Client) EstimatedTableRowsCount(table string, opts RowsOptions) (*Result, error) {
	schema, table := getSchemaAndTable(table)
	result, err := client.query(statements.EstimatedTableRowCount, schema, table)
	if err != nil {
		return nil, err
	}
	// float64 to int64 conversion
	estimatedRowsCount := result.Rows[0][0].(float64)
	result.Rows[0] = Row{int64(estimatedRowsCount)}

	return result, nil
}

func (client *Client) TableRowsCount(table string, opts RowsOptions) (*Result, error) {
	// Return postgres estimated rows count on empty filter
	if opts.Where == "" && client.serverType == postgresType {
		res, err := client.EstimatedTableRowsCount(table, opts)
		if err != nil {
			return nil, err
		}
		n := res.Rows[0][0].(int64)
		if n >= 100000 {
			return res, nil
		}
	}

	schema, tableName := getSchemaAndTable(table)
	sql := fmt.Sprintf(`SELECT COUNT(1) FROM "%s"."%s"`, schema, tableName)

	if opts.Where != "" {
		sql += fmt.Sprintf(" WHERE %s", opts.Where)
	}

	return client.query(sql)
}

func (client *Client) TableInfo(table string) (*Result, error) {
	if client.serverType == cockroachType {
		return client.query(statements.TableInfoCockroach)
	}
	schema, table := getSchemaAndTable(table)
	return client.query(statements.TableInfo, fmt.Sprintf(`"%s"."%s"`, schema, table))
}

func (client *Client) TableIndexes(table string) (*Result, error) {
	schema, table := getSchemaAndTable(table)
	res, err := client.query(statements.TableIndexes, schema, table)

	if err != nil {
		return nil, err
	}

	return res, err
}

func (client *Client) TableConstraints(table string) (*Result, error) {
	schema, table := getSchemaAndTable(table)
	res, err := client.query(statements.TableConstraints, schema, table)

	if err != nil {
		return nil, err
	}

	return res, err
}

func (client *Client) TablesStats() (*Result, error) {
	return client.query(statements.TablesStats)
}

func (client *Client) ServerSettings() (*Result, error) {
	return client.query(statements.Settings)
}

// Returns all active queriers on the server
func (client *Client) Activity() (*Result, error) {
	if client.serverType == cockroachType {
		return client.query("SHOW QUERIES")
	}

	version := getMajorMinorVersionString(client.serverVersion)
	query := statements.Activity[version]
	if query == "" {
		query = statements.Activity["default"]
	}

	return client.query(query)
}

func (client *Client) Query(query string) (*Result, error) {
	res, err := client.query(query)

	// Save history records only if query did not fail
	if err == nil && !client.hasHistoryRecord(query) {
		client.History = append(client.History, history.NewRecord(query))
	}

	return res, err
}

func (client *Client) SetReadOnlyMode() error {
	var value string
	if err := client.db.Get(&value, "SHOW default_transaction_read_only;"); err != nil {
		return err
	}

	if value == "off" {
		_, err := client.db.Exec("SET default_transaction_read_only=on;")
		return err
	}

	return nil
}

func (client *Client) ServerVersionInfo() string {
	return fmt.Sprintf("%s %s", client.serverType, client.serverVersion)
}

func (client *Client) ServerVersion() string {
	return client.serverVersion
}

func (client *Client) context() (context.Context, context.CancelFunc) {
	if client.queryTimeout > 0 {
		return context.WithTimeout(context.Background(), client.queryTimeout)
	}
	return context.Background(), func() {}
}

func (client *Client) exec(query string, args ...interface{}) (*Result, error) {
	ctx, cancel := client.context()
	defer cancel()

	queryStart := time.Now()
	res, err := client.db.ExecContext(ctx, query, args...)
	queryFinish := time.Now()
	if err != nil {
		return nil, err
	}

	affected, err := res.RowsAffected()
	if err != nil {
		return nil, err
	}

	result := Result{
		Columns: []string{"Rows Affected"},
		Rows: []Row{
			{affected},
		},
		Stats: &ResultStats{
			ColumnsCount:    1,
			RowsCount:       1,
			QueryStartTime:  queryStart.UTC(),
			QueryFinishTime: queryFinish.UTC(),
			QueryDuration:   queryFinish.Sub(queryStart).Milliseconds(),
		},
	}

	return &result, nil
}

func (client *Client) query(query string, args ...interface{}) (*Result, error) {
	if client.db == nil {
		return nil, nil
	}

	// Update the last usage time
	defer func() {
		client.lastQueryTime = time.Now().UTC()
	}()

	// We're going to force-set transaction mode on every query.
	// This is needed so that default mode could not be changed by user.
	if command.Opts.ReadOnly || client.readonly {
		if err := client.SetReadOnlyMode(); err != nil {
			return nil, err
		}
		if containsRestrictedKeywords(query) {
			return nil, errors.New("query contains keywords not allowed in read-only mode")
		}
	}

	action := strings.ToLower(strings.Split(query, " ")[0])
	hasReturnValues := strings.Contains(strings.ToLower(query), " returning ")

	if (action == "update" || action == "delete") && !hasReturnValues {
		return client.exec(query, args...)
	}

	ctx, cancel := client.context()
	defer cancel()

	queryStart := time.Now()
	rows, err := client.db.QueryxContext(ctx, query, args...)
	queryFinish := time.Now()
	if err != nil {
		if command.Opts.Debug {
			log.Println("Failed query:", query, "\nArgs:", args)
		}
		return nil, err
	}
	defer rows.Close()

	cols, err := rows.Columns()
	if err != nil {
		return nil, err
	}

	// Make sure to never return null columns
	if cols == nil {
		cols = []string{}
	}

	result := Result{
		Columns: cols,
		Rows:    []Row{},
	}

	for rows.Next() {
		obj, err := rows.SliceScan()

		for i, item := range obj {
			if item == nil {
				obj[i] = nil
			} else {
				t := reflect.TypeOf(item).Kind().String()

				if t == "slice" {
					obj[i] = string(item.([]byte))
				}
			}
		}

		if err == nil {
			result.Rows = append(result.Rows, obj)
		}
	}

	result.Stats = &ResultStats{
		ColumnsCount:    len(cols),
		RowsCount:       len(result.Rows),
		QueryStartTime:  queryStart.UTC(),
		QueryFinishTime: queryFinish.UTC(),
		QueryDuration:   queryFinish.Sub(queryStart).Milliseconds(),
	}

	result.PostProcess()

	return &result, nil
}

// Close database connection
func (client *Client) Close() error {
	if client.closed {
		return nil
	}
	defer func() {
		client.closed = true
		client.tunnel = nil
	}()

	if client.tunnel != nil {
		client.tunnel.Close()
	}

	if client.db != nil {
		return client.db.Close()
	}

	return nil
}

func (c *Client) IsClosed() bool {
	return c.closed
}

func (c *Client) LastQueryTime() time.Time {
	return c.lastQueryTime
}

func (client *Client) IsIdle() bool {
	mins := int(time.Since(client.lastQueryTime).Minutes())

	if command.Opts.ConnectionIdleTimeout > 0 {
		return mins >= command.Opts.ConnectionIdleTimeout
	}

	return false
}

// Fetch all rows as strings for a single column
func (client *Client) fetchRows(q string) ([]string, error) {
	res, err := client.query(q)

	if err != nil {
		return nil, err
	}

	// Init empty slice so json.Marshal will encode it to "[]" instead of "null"
	results := make([]string, 0)

	for _, row := range res.Rows {
		results = append(results, row[0].(string))
	}

	return results, nil
}

func (client *Client) hasHistoryRecord(query string) bool {
	result := false

	for _, record := range client.History {
		if record.Query == query {
			result = true
			break
		}
	}

	return result
}

type ConnContext struct {
	Host     string
	User     string
	Database string
	Mode     string
}

func (c ConnContext) String() string {
	return fmt.Sprintf(
		"host=%q user=%q database=%q mode=%q",
		c.Host, c.User, c.Database, c.Mode,
	)
}

// ConnContext returns information about current database connection
func (client *Client) GetConnContext() (*ConnContext, error) {
	url, err := neturl.Parse(client.ConnectionString)
	if err != nil {
		return nil, err
	}

	ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
	defer cancel()

	connCtx := ConnContext{
		Host: url.Hostname(),
		Mode: "default",
	}

	if command.Opts.ReadOnly {
		connCtx.Mode = "readonly"
	}

	row := client.db.QueryRowContext(ctx, "SELECT current_user, current_database()")
	if err := row.Scan(&connCtx.User, &connCtx.Database); err != nil {
		return nil, err
	}

	return &connCtx, nil
}


================================================
FILE: pkg/client/client_test.go
================================================
package client

import (
	"fmt"
	"log"
	"os"
	"os/exec"
	"runtime"
	"sort"
	"strings"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"github.com/sosedoff/pgweb/pkg/command"
)

var (
	testClient     *Client
	testCommands   map[string]string
	serverHost     string
	serverPort     string
	serverUser     string
	serverPassword string
	serverDatabase string
)

func mapKeys(data map[string]*Objects) []string {
	result := []string{}
	for k := range data {
		result = append(result, k)
	}
	return result
}

func objectNames(data []Object) []string {
	names := make([]string, len(data))
	for i, obj := range data {
		names[i] = obj.Name
	}

	sort.Strings(names)
	return names
}

// assertMatches is a helper method to check if src slice contains any elements of expected slice
func assertMatches(t *testing.T, expected, src []string) {
	assert.NotEqual(t, 0, len(expected))
	assert.NotEqual(t, 0, len(src))

	for _, val := range expected {
		assert.Contains(t, src, val)
	}
}

func pgVersion() (int, int) {
	var major, minor int
	if _, err := fmt.Sscanf(os.Getenv("PGVERSION"), "%d.%d", &major, &minor); err != nil {
		log.Println("[warn] unable to read value of PGVERSION env var")
	}
	return major, minor
}

func getVar(name, def string) string {
	val := os.Getenv(name)
	if val == "" {
		return def
	}
	return val
}

func initVars() {
	// We need to load default options to make sure all stuff works
	if err := command.SetDefaultOptions(); err != nil {
		log.Fatal(err)
	}

	serverHost = getVar("PGHOST", "localhost")
	serverPort = getVar("PGPORT", "5432")
	serverUser = getVar("PGUSER", "postgres")
	serverPassword = getVar("PGPASSWORD", "postgres")
	serverDatabase = getVar("PGDATABASE", "booktown")
}

func setupCommands() {
	testCommands = map[string]string{
		"createdb": "createdb",
		"psql":     "psql",
		"dropdb":   "dropdb",
	}

	if onWindows() {
		for k, v := range testCommands {
			testCommands[k] = v + ".exe"
		}
	}
}

func onWindows() bool {
	return runtime.GOOS == "windows"
}

func setup() {
	// No pretty JSON for tests
	command.Opts.DisablePrettyJSON = true

	out, err := exec.Command(
		testCommands["createdb"],
		"-U", serverUser,
		"-h", serverHost,
		"-p", serverPort,
		serverDatabase,
	).CombinedOutput()

	if err != nil {
		fmt.Println("Database creation failed:", string(out))
		fmt.Println("Error:", err)
		os.Exit(1)
	}

	out, err = exec.Command(
		testCommands["psql"],
		"-U", serverUser,
		"-h", serverHost,
		"-p", serverPort,
		"-f", "../../data/booktown.sql",
		serverDatabase,
	).CombinedOutput()

	if err != nil {
		fmt.Println("Database import failed:", string(out))
		fmt.Println("Error:", err)
		os.Exit(1)
	}
}

func setupClient() {
	url := fmt.Sprintf("postgres://%s@%s:%s/%s?sslmode=disable", serverUser, serverHost, serverPort, serverDatabase)
	testClient, _ = NewFromUrl(url, nil)
}

func teardownClient() {
	if testClient != nil {
		testClient.Close()
	}
}

func teardown(t *testing.T, allowFail bool) {
	output, err := exec.Command(
		testCommands["dropdb"],
		"-U", serverUser,
		"-h", serverHost,
		"-p", serverPort,
		serverDatabase,
	).CombinedOutput()

	if err != nil && strings.Contains(err.Error(), "does not exist") {
		t.Log("Teardown error:", err)
		t.Logf("%s\n", output)

		if !allowFail {
			assert.NoError(t, err)
		}
	}
}

func testNewClientFromURL(t *testing.T) {
	t.Run("postgres prefix", func(t *testing.T) {
		url := fmt.Sprintf("postgres://%s@%s:%s/%s?sslmode=disable", serverUser, serverHost, serverPort, serverDatabase)
		client, err := NewFromUrl(url, nil)

		assert.Equal(t, nil, err)
		assert.Equal(t, url, client.ConnectionString)
		assert.NoError(t, client.Close())
	})

	t.Run("postgresql prefix", func(t *testing.T) {
		url := fmt.Sprintf("postgresql://%s@%s:%s/%s?sslmode=disable", serverUser, serverHost, serverPort, serverDatabase)
		client, err := NewFromUrl(url, nil)

		assert.Equal(t, nil, err)
		assert.Equal(t, url, client.ConnectionString)
		assert.NoError(t, client.Close())
	})
}

func testClientIdleTime(t *testing.T) {
	examples := map[time.Time]bool{
		time.Now():                         false, // Current time
		time.Now().Add(time.Minute * -30):  false, // 30 minutes ago
		time.Now().Add(time.Minute * -240): true,  // 240 minutes ago
		time.Now().Add(time.Minute * 30):   false, // 30 minutes in future
		time.Now().Add(time.Minute * 128):  false, // 128 minutes in future
	}

	for ts, expected := range examples {
		testClient.lastQueryTime = ts
		assert.Equal(t, expected, testClient.IsIdle())
	}
}

func testTest(t *testing.T) {
	examples := []struct {
		name  string
		input string
		err   error
	}{
		{
			name:  "success",
			input: fmt.Sprintf("postgres://%s@%s:%s/%s?sslmode=disable", serverUser, serverHost, serverPort, serverDatabase),
			err:   nil,
		},
		{
			name:  "connection refused",
			input: "postgresql://localhost:5433/dbname",
			err:   ErrConnectionRefused,
		},
		{
			name:  "invalid user",
			input: fmt.Sprintf("postgres://%s:%s@%s:%s/%s?sslmode=disable", "foo", serverPassword, serverHost, serverPort, serverDatabase),
			err:   ErrAuthFailed,
		},
		{
			name:  "invalid password",
			input: fmt.Sprintf("postgres://%s:%s@%s:%s/%s?sslmode=disable", serverUser, "foo", serverHost, serverPort, serverDatabase),
			err:   ErrAuthFailed,
		},
		{
			name:  "invalid database",
			input: fmt.Sprintf("postgres://%s@%s:%s/%s?sslmode=disable", serverUser, serverHost, serverPort, "foo"),
			err:   ErrDatabaseNotExist,
		},
	}

	for _, ex := range examples {
		t.Run(ex.name, func(t *testing.T) {
			conn, err := NewFromUrl(ex.input, nil)
			require.NoError(t, err)

			require.Equal(t, ex.err, conn.Test())
		})
	}
}

func testInfo(t *testing.T) {
	t.Run("normal", func(t *testing.T) {
		expected := []string{
			"session_user",
			"current_user",
			"current_database",
			"current_schemas",
			"inet_client_addr",
			"inet_client_port",
			"inet_server_addr",
			"inet_server_port",
			"version",
		}

		res, err := testClient.Info()
		assert.NoError(t, err)
		assert.Equal(t, expected, res.Columns)
	})

	t.Run("with restrictions", func(t *testing.T) {
		expected := []string{
			"session_user",
			"current_user",
			"current_database",
			"current_schemas",
			"version",
		}

		// Prepare a new user and database
		testClient.db.MustExec("DROP DATABASE IF EXISTS testdb")
		testClient.db.Exec("DROP OWNED BY IF EXISTS testuser") //nolint:all
		testClient.db.MustExec("DROP ROLE IF EXISTS testuser")
		testClient.db.MustExec("CREATE ROLE testuser WITH PASSWORD 'secret' LOGIN NOSUPERUSER NOINHERIT")
		testClient.db.MustExec("CREATE DATABASE testdb OWNER testuser")

		// Disable access to inet_ calls for new user
		url := fmt.Sprintf("postgres://%s:@%s:%s/testdb?sslmode=disable", serverUser, serverHost, serverPort)
		client, err := NewFromUrl(url, nil)
		assert.NoError(t, err)
		client.db.MustExec("REVOKE EXECUTE ON FUNCTION inet_client_addr() FROM PUBLIC")
		assert.NoError(t, client.Close())

		// Connect using new user
		url = fmt.Sprintf("postgres://testuser:secret@%s:%s/testdb?sslmode=disable", serverHost, serverPort)
		client, err = NewFromUrl(url, nil)
		assert.NoError(t, err)
		defer client.Close()

		res, err := client.Info()
		assert.NoError(t, err)
		assert.Equal(t, expected, res.Columns)
	})
}

func testActivity(t *testing.T) {
	expected := []string{"datid", "pid", "query", "query_start", "state", "client_addr"}

	res, err := testClient.Activity()
	assert.NoError(t, err)
	assertMatches(t, expected, res.Columns)
}

func testDatabases(t *testing.T) {
	res, err := testClient.Databases()
	assert.NoError(t, err)
	assertMatches(t, []string{"booktown", "postgres"}, res)
}

func testSchemas(t *testing.T) {
	res, err := testClient.Schemas()
	assert.NoError(t, err)
	assert.Equal(t, []string{"public"}, res)
}

func testObjects(t *testing.T) {
	res, err := testClient.Objects()
	objects := ObjectsFromResult(res)

	tables := []string{
		"alternate_stock",
		"authors",
		"book_backup",
		"book_queue",
		"books",
		"customers",
		"daily_inventory",
		"distinguished_authors",
		"dummies",
		"editions",
		"employees",
		"favorite_authors",
		"favorite_books",
		"money_example",
		"my_list",
		"numeric_values",
		"publishers",
		"schedules",
		"shipments",
		"states",
		"stock",
		"stock_backup",
		"subjects",
		"text_sorting",
	}

	functions := []string{
		"add_shipment",
		"add_two_loop",
		"books_by_subject",
		"compound_word",
		"count_by_two",
		"double_price",
		"extract_all_titles",
		"extract_all_titles2",
		"extract_title",
		"first",
		"get_author",
		"get_author",
		"get_customer_id",
		"get_customer_name",
		"html_linebreaks",
		"in_stock",
		"isbn_to_title",
		"mixed",
		"raise_test",
		"ship_item",
		"stock_amount",
		"test",
		"title",
		"triple_price",
	}

	assert.NoError(t, err)
	assert.Equal(t, []string{"oid", "schema", "name", "type", "owner", "comment"}, res.Columns)
	assert.Equal(t, []string{"public"}, mapKeys(objects))
	assert.Equal(t, tables, objectNames(objects["public"].Tables))
	assertMatches(t, functions, objectNames(objects["public"].Functions))
	assert.Equal(t, []string{"recent_shipments", "stock_view"}, objectNames(objects["public"].Views))
	assert.Equal(t, []string{"author_ids", "book_ids", "shipments_ship_id_seq", "subject_ids"}, objectNames(objects["public"].Sequences))

	major, minor := pgVersion()
	if minor == 0 || minor >= 3 {
		assert.Equal(t, []string{"m_stock_view"}, objectNames(objects["public"].MaterializedViews))
	} else {
		t.Logf("Skipping materialized view on %d.%d\n", major, minor)
	}
}

func testTable(t *testing.T) {
	columns := []string{
		"column_name",
		"data_type",
		"is_nullable",
		"character_maximum_length",
		"character_set_catalog",
		"column_default",
		"comment",
	}

	res, err := testClient.Table("books")
	assert.NoError(t, err)
	assert.Equal(t, columns, res.Columns)
	assert.Equal(t, 4, len(res.Rows))
}

func testTableRows(t *testing.T) {
	res, err := testClient.TableRows("books", RowsOptions{})
	assert.NoError(t, err)
	assert.Equal(t, 4, len(res.Columns))
	assert.Equal(t, 15, len(res.Rows))
}

func testTableInfo(t *testing.T) {
	res, err := testClient.TableInfo("books")
	assert.NoError(t, err)
	assert.Equal(t, 4, len(res.Columns))
	assert.Equal(t, 1, len(res.Rows))
}

func testEstimatedTableRowsCount(t *testing.T) {
	res, err := testClient.EstimatedTableRowsCount("books", RowsOptions{})
	assert.NoError(t, err)
	assert.Equal(t, []string{"reltuples"}, res.Columns)
	assert.Equal(t, []Row{{int64(15)}}, res.Rows)
}

func testTableRowsCount(t *testing.T) {
	res, err := testClient.TableRowsCount("books", RowsOptions{})
	assert.NoError(t, err)
	assert.Equal(t, []string{"count"}, res.Columns)
	assert.Equal(t, []Row{{int64(15)}}, res.Rows)
}

func testTableRowsCountWithLargeTable(t *testing.T) {
	testClient.db.MustExec(`CREATE TABLE large_table AS SELECT s FROM generate_series(1,1000000) s;`)
	testClient.db.MustExec(`VACUUM large_table;`)

	res, err := testClient.TableRowsCount("large_table", RowsOptions{})
	assert.Equal(t, nil, err)
	assert.Equal(t, []string{"reltuples"}, res.Columns)
	assert.Equal(t, []Row{{int64(1000000)}}, res.Rows)
}

func testTableIndexes(t *testing.T) {
	res, err := testClient.TableIndexes("books")
	assert.NoError(t, err)
	assert.Equal(t, []string{"index_name", "index_size", "index_definition"}, res.Columns)
	assert.Equal(t, 2, len(res.Rows))
}

func testTableConstraints(t *testing.T) {
	res, err := testClient.TableConstraints("editions")
	assert.NoError(t, err)
	assert.Equal(t, []string{"name", "definition"}, res.Columns)
	assert.Equal(t, Row{"pkey", "PRIMARY KEY (isbn)"}, res.Rows[0])
	assert.Equal(t, Row{"integrity", "CHECK (book_id IS NOT NULL AND edition IS NOT NULL)"}, res.Rows[1])
}

func testTableNameWithCamelCase(t *testing.T) {
	testClient.db.MustExec(`CREATE TABLE "exampleTable" (id int, name varchar);`)
	testClient.db.MustExec(`INSERT INTO "exampleTable" (id, name) VALUES (1, 'foo'), (2, 'bar');`)

	_, err := testClient.Table("exampleTable")
	assert.NoError(t, err)

	_, err = testClient.TableInfo("exampleTable")
	assert.NoError(t, err)

	_, err = testClient.TableConstraints("exampleTable")
	assert.NoError(t, err)

	_, err = testClient.TableIndexes("exampleTable")
	assert.NoError(t, err)

	_, err = testClient.TableRowsCount("exampleTable", RowsOptions{})
	assert.NoError(t, err)

	_, err = testClient.EstimatedTableRowsCount("exampleTable", RowsOptions{})
	assert.NoError(t, err)
}

func testQuery(t *testing.T) {
	t.Run("basic query", func(t *testing.T) {
		res, err := testClient.Query("SELECT * FROM books")
		assert.NoError(t, err)
		assert.Equal(t, 4, len(res.Columns))
		assert.Equal(t, 15, len(res.Rows))
	})

	t.Run("error", func(t *testing.T) {
		res, err := testClient.Query("SELCT * FROM books")
		assert.NotNil(t, err)
		assert.Equal(t, "pq: syntax error at or near \"SELCT\"", err.Error())
		assert.Nil(t, res)
	})

	t.Run("invalid table", func(t *testing.T) {
		res, err := testClient.Query("SELECT * FROM books2")
		assert.NotNil(t, err)
		assert.Equal(t, "pq: relation \"books2\" does not exist", err.Error())
		assert.Nil(t, res)
	})

	t.Run("timeout", func(t *testing.T) {
		testClient.queryTimeout = 
Download .txt
gitextract_uzqtco04/

├── .claude/
│   └── settings.local.json
├── .dockerignore
├── .gitattributes
├── .github/
│   └── workflows/
│       ├── checks.yml
│       ├── deploy.yml
│       ├── docker.yml
│       └── release.yml
├── .gitignore
├── .golangci.yml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── Dockerfile
├── LICENSE
├── Makefile
├── Procfile
├── README.md
├── SCREENS.md
├── config/
│   ├── examples/
│   │   ├── connect_backend_go/
│   │   │   ├── README.md
│   │   │   └── main.go
│   │   └── connect_backend_ruby/
│   │       ├── Gemfile
│   │       ├── README.md
│   │       ├── config.ru
│   │       └── main.rb
│   ├── pgweb.freebsd_rc
│   ├── pgweb.service
│   ├── pgweb_initd.conf
│   └── pgweb_upstart.conf
├── data/
│   ├── bookmark.toml
│   ├── bookmark_invalid_ssl.toml
│   ├── bookmark_url.toml
│   ├── bookmark_with_ssh.toml
│   ├── booktown.sql
│   ├── invalid.toml
│   ├── lc_example1.sql
│   ├── lc_example2.sql
│   ├── lc_invalid_meta.sql
│   ├── lc_no_meta.sql
│   ├── passfile
│   └── roach.sql
├── docker-compose-pg.yml
├── docker-compose.yml
├── fly.toml
├── go.mod
├── go.sum
├── main.go
├── pkg/
│   ├── api/
│   │   ├── api.go
│   │   ├── api_test.go
│   │   ├── errors.go
│   │   ├── helpers.go
│   │   ├── helpers_test.go
│   │   ├── logger.go
│   │   ├── logger_test.go
│   │   ├── middleware.go
│   │   ├── routes.go
│   │   ├── session_manager.go
│   │   ├── session_manager_test.go
│   │   └── types.go
│   ├── bookmarks/
│   │   ├── bookmarks.go
│   │   ├── bookmarks_test.go
│   │   ├── manager.go
│   │   └── manager_test.go
│   ├── cli/
│   │   └── cli.go
│   ├── client/
│   │   ├── client.go
│   │   ├── client_test.go
│   │   ├── codec.go
│   │   ├── codec_test.go
│   │   ├── dump.go
│   │   ├── dump_test.go
│   │   ├── result.go
│   │   ├── result_test.go
│   │   ├── tunnel.go
│   │   ├── util.go
│   │   └── util_test.go
│   ├── command/
│   │   ├── options.go
│   │   ├── options_test.go
│   │   └── version.go
│   ├── connect/
│   │   ├── backend.go
│   │   ├── backend_test.go
│   │   └── types.go
│   ├── connection/
│   │   ├── connection_string.go
│   │   ├── connection_string_test.go
│   │   ├── port.go
│   │   └── port_test.go
│   ├── history/
│   │   └── history.go
│   ├── metrics/
│   │   ├── handler.go
│   │   ├── metrics.go
│   │   └── server.go
│   ├── queries/
│   │   ├── field.go
│   │   ├── field_test.go
│   │   ├── metadata.go
│   │   ├── metadata_test.go
│   │   ├── query.go
│   │   ├── query_test.go
│   │   ├── store.go
│   │   └── store_test.go
│   ├── shared/
│   │   └── ssh_info.go
│   ├── statements/
│   │   ├── sql/
│   │   │   ├── databases.sql
│   │   │   ├── estimated_row_count.sql
│   │   │   ├── function.sql
│   │   │   ├── info.sql
│   │   │   ├── info_simple.sql
│   │   │   ├── materialized_view.sql
│   │   │   ├── objects.sql
│   │   │   ├── schemas.sql
│   │   │   ├── settings.sql
│   │   │   ├── table_constraints.sql
│   │   │   ├── table_indexes.sql
│   │   │   ├── table_info.sql
│   │   │   ├── table_info_cockroach.sql
│   │   │   ├── table_schema.sql
│   │   │   └── tables_stats.sql
│   │   └── sql.go
│   └── util/
│       └── profiler.go
├── script/
│   ├── build_all.sh
│   ├── check_formatting.sh
│   ├── package.sh
│   ├── test_all.sh
│   ├── test_cockroach.sh
│   └── update_homebrew.sh
└── static/
    ├── css/
    │   ├── app.css
    │   ├── bootstrap.css
    │   └── font-awesome.css
    ├── data.go
    ├── fonts/
    │   └── FontAwesome.otf
    ├── index.html
    └── js/
        ├── ace-pgsql.js
        ├── ace.js
        ├── app.js
        ├── base64.js
        ├── bootstrap-contextmenu.js
        ├── bootstrap-dropdown.js
        ├── ext-language_tools.js
        ├── jquery.js
        ├── theme-tomorrow.js
        └── utils.js
Download .txt
SYMBOL INDEX (681 symbols across 64 files)

FILE: config/examples/connect_backend_go/main.go
  type BackendRequest (line 10) | type BackendRequest struct
  type BackendResponse (line 16) | type BackendResponse struct
  function main (line 20) | func main() {

FILE: config/examples/connect_backend_ruby/main.rb
  function error (line 15) | def error(code, message)

FILE: data/booktown.sql
  type "dummies" (line 20) | CREATE TABLE "dummies" (
  type "books" (line 37) | CREATE TABLE "books" (
  function "plpgsql_call_handler" (line 51) | CREATE FUNCTION "plpgsql_call_handler" () RETURNS opaque AS '/usr/local/...
  function "audit_bk" (line 67) | CREATE FUNCTION "audit_bk" (integer) RETURNS integer AS '
  function "audit" (line 88) | CREATE FUNCTION "audit" (integer) RETURNS integer AS '
  function "auditbk" (line 109) | CREATE FUNCTION "auditbk" () RETURNS integer AS '
  function "audit_bk1" (line 130) | CREATE FUNCTION "audit_bk1" () RETURNS opaque AS '
  function "test_check_a_id" (line 151) | CREATE FUNCTION "test_check_a_id" () RETURNS opaque AS '
  function "audit_test" (line 176) | CREATE FUNCTION "audit_test" () RETURNS opaque AS '
  function "first" (line 203) | CREATE FUNCTION "first" () RETURNS integer AS '
  function "test" (line 217) | CREATE FUNCTION "test" (integer) RETURNS integer AS '
  function "you_me" (line 234) | CREATE FUNCTION "you_me" (integer) RETURNS integer AS '
  function "count_by_two" (line 248) | CREATE FUNCTION "count_by_two" (integer) RETURNS integer AS '
  function "me" (line 268) | CREATE FUNCTION "me" () RETURNS text AS '
  function "display_cust" (line 282) | CREATE FUNCTION "display_cust" (integer) RETURNS text AS '
  function "mixed" (line 307) | CREATE FUNCTION "mixed" () RETURNS integer AS '
  type "publishers" (line 326) | CREATE TABLE "publishers" (
  function "compound_word" (line 339) | CREATE FUNCTION "compound_word" (text,text) RETURNS text AS '
  function "givename" (line 356) | CREATE FUNCTION "givename" () RETURNS opaque AS '
  type "authors" (line 373) | CREATE TABLE "authors" (
  type "states" (line 386) | CREATE TABLE "states" (
  type "my_list" (line 399) | CREATE TABLE "my_list" (
  type "stock" (line 409) | CREATE TABLE "stock" (
  type "numeric_values" (line 431) | CREATE TABLE "numeric_values" (
  type "daily_inventory" (line 441) | CREATE TABLE "daily_inventory" (
  type "money_example" (line 452) | CREATE TABLE "money_example" (
  type "shipments" (line 463) | CREATE TABLE "shipments" (
  type "customers" (line 476) | CREATE TABLE "customers" (
  type "book_queue" (line 497) | CREATE TABLE "book_queue" (
  function "title" (line 510) | CREATE FUNCTION "title" (integer) RETURNS text AS 'SELECT title from boo...
  type "stock_backup" (line 518) | CREATE TABLE "stock_backup" (
  function "double_price" (line 531) | CREATE FUNCTION "double_price" (double precision) RETURNS double precisi...
  function "triple_price" (line 544) | CREATE FUNCTION "triple_price" (double precision) RETURNS double precisi...
  function "stock_amount" (line 563) | CREATE FUNCTION "stock_amount" (integer,integer) RETURNS integer AS '
  function "in_stock" (line 606) | CREATE FUNCTION "in_stock" (integer,integer) RETURNS boolean AS '
  function "extract_all_titles" (line 652) | CREATE FUNCTION "extract_all_titles" () RETURNS text AS '
  function "books_by_subject" (line 688) | CREATE FUNCTION "books_by_subject" (text) RETURNS text AS '
  function "add_two_loop" (line 716) | CREATE FUNCTION "add_two_loop" (integer,integer) RETURNS integer AS '
  function "extract_all_titles2" (line 744) | CREATE FUNCTION "extract_all_titles2" () RETURNS text AS '
  function "extract_title" (line 775) | CREATE FUNCTION "extract_title" (integer) RETURNS text AS '
  function "raise_test" (line 797) | CREATE FUNCTION "raise_test" () RETURNS integer AS '
  function "add_shipment" (line 832) | CREATE FUNCTION "add_shipment" (integer,text) RETURNS timestamp with tim...
  function "ship_item" (line 853) | CREATE FUNCTION "ship_item" (text,text,text) RETURNS integer AS '
  function "check_book_addition" (line 887) | CREATE FUNCTION "check_book_addition" () RETURNS opaque AS '
  type "stock_view" (line 917) | CREATE VIEW "stock_view" as SELECT stock.isbn, stock.retail, stock.stock...
  type "favorite_books" (line 927) | CREATE TABLE "favorite_books" (
  function "check_shipment_addition" (line 946) | CREATE FUNCTION "check_shipment_addition" () RETURNS opaque AS '
  type "employees" (line 989) | CREATE TABLE "employees" (
  type "editions" (line 1003) | CREATE TABLE "editions" (
  type "distinguished_authors" (line 1028) | CREATE TABLE "distinguished_authors" (
  function "isbn_to_title" (line 1039) | CREATE FUNCTION "isbn_to_title" (text) RETURNS text AS 'SELECT title FRO...
  type "favorite_authors" (line 1050) | CREATE TABLE "favorite_authors" (
  function "get_customer_name" (line 1061) | CREATE FUNCTION "get_customer_name" (integer) RETURNS text AS '
  function "get_customer_id" (line 1090) | CREATE FUNCTION "get_customer_id" (text,text) RETURNS integer AS '
  function "get_author" (line 1118) | CREATE FUNCTION "get_author" (text) RETURNS text AS '
  function "get_author" (line 1150) | CREATE FUNCTION "get_author" (integer) RETURNS text AS '
  function "html_linebreaks" (line 1180) | CREATE FUNCTION "html_linebreaks" (text) RETURNS text AS '
  type "text_sorting" (line 1202) | CREATE TABLE "text_sorting" (
  type "subjects" (line 1212) | CREATE TABLE "subjects" (
  type "alternate_stock" (line 1233) | CREATE TABLE "alternate_stock" (
  type "book_backup" (line 1246) | CREATE TABLE "book_backup" (
  function "sync_authors_and_books" (line 1259) | CREATE FUNCTION "sync_authors_and_books" () RETURNS opaque AS '
  type "schedules" (line 1274) | CREATE TABLE "schedules" (
  type "recent_shipments" (line 1286) | CREATE VIEW "recent_shipments" as SELECT count(*) AS num_shipped, max(sh...

FILE: data/roach.sql
  type product_information (line 5) | CREATE TABLE product_information (
  type customers (line 30) | CREATE TABLE customers (
  type orders (line 35) | CREATE TABLE orders (

FILE: main.go
  function main (line 7) | func main() {

FILE: pkg/api/api.go
  function DB (line 38) | func DB(c *gin.Context) *client.Client {
  function setClient (line 46) | func setClient(c *gin.Context, newClient *client.Client) error {
  function GetHome (line 67) | func GetHome(prefix string) http.Handler {
  function GetAssets (line 74) | func GetAssets(prefix string) http.Handler {
  function GetSessions (line 84) | func GetSessions(c *gin.Context) {
  function ConnectWithBackend (line 95) | func ConnectWithBackend(c *gin.Context) {
  function Connect (line 145) | func Connect(c *gin.Context) {
  function ConnectWithURL (line 187) | func ConnectWithURL(c *gin.Context) (*client.Client, error) {
  function ConnectWithBookmark (line 209) | func ConnectWithBookmark(id string) (*client.Client, error) {
  function SwitchDb (line 221) | func SwitchDb(c *gin.Context) {
  function Disconnect (line 283) | func Disconnect(c *gin.Context) {
  function RunQuery (line 312) | func RunQuery(c *gin.Context) {
  function ExplainQuery (line 324) | func ExplainQuery(c *gin.Context) {
  function AnalyzeQuery (line 336) | func AnalyzeQuery(c *gin.Context) {
  function GetDatabases (line 348) | func GetDatabases(c *gin.Context) {
  function GetObjects (line 364) | func GetObjects(c *gin.Context) {
  function GetSchemas (line 374) | func GetSchemas(c *gin.Context) {
  function GetTable (line 380) | func GetTable(c *gin.Context) {
  function GetTableRows (line 402) | func GetTableRows(c *gin.Context) {
  function GetTableInfo (line 455) | func GetTableInfo(c *gin.Context) {
  function GetHistory (line 465) | func GetHistory(c *gin.Context) {
  function GetConnectionInfo (line 470) | func GetConnectionInfo(c *gin.Context) {
  function GetServerSettings (line 491) | func GetServerSettings(c *gin.Context) {
  function GetActivity (line 497) | func GetActivity(c *gin.Context) {
  function GetTableIndexes (line 503) | func GetTableIndexes(c *gin.Context) {
  function GetTableConstraints (line 509) | func GetTableConstraints(c *gin.Context) {
  function GetTablesStats (line 515) | func GetTablesStats(c *gin.Context) {
  function HandleQuery (line 556) | func HandleQuery(query string, c *gin.Context) {
  function GetBookmarks (line 594) | func GetBookmarks(c *gin.Context) {
  function GetInfo (line 601) | func GetInfo(c *gin.Context) {
  function DataExport (line 614) | func DataExport(c *gin.Context) {
  function GetFunction (line 656) | func GetFunction(c *gin.Context) {
  function GetLocalQueries (line 661) | func GetLocalQueries(c *gin.Context) {
  function RunLocalQuery (line 691) | func RunLocalQuery(c *gin.Context) {

FILE: pkg/api/api_test.go
  function Test_assetContentType (line 9) | func Test_assetContentType(t *testing.T) {

FILE: pkg/api/helpers.go
  type Error (line 48) | type Error struct
  function NewError (line 52) | func NewError(err error) Error {
  function cleanQuery (line 57) | func cleanQuery(query string) string {
  function desanitize64 (line 71) | func desanitize64(query string) string {
  function sanitizeFilename (line 81) | func sanitizeFilename(str string) string {
  function getSessionId (line 86) | func getSessionId(req *http.Request) string {
  function getQueryParam (line 94) | func getQueryParam(c *gin.Context, name string) string {
  function parseIntFormValue (line 105) | func parseIntFormValue(c *gin.Context, name string, defValue int) (int, ...
  function parseSshInfo (line 124) | func parseSshInfo(c *gin.Context) *shared.SSHInfo {
  function assetContentType (line 141) | func assetContentType(name string) string {
  function serveResult (line 157) | func serveResult(c *gin.Context, result interface{}, err interface{}) {
  function successResponse (line 167) | func successResponse(c *gin.Context, data interface{}) {
  function errorResponse (line 172) | func errorResponse(c *gin.Context, status int, err interface{}) {
  function badRequest (line 188) | func badRequest(c *gin.Context, err interface{}) {

FILE: pkg/api/helpers_test.go
  function Test_desanitize64 (line 14) | func Test_desanitize64(t *testing.T) {
  function Test_cleanQuery (line 27) | func Test_cleanQuery(t *testing.T) {
  function Test_sanitizeFilename (line 33) | func Test_sanitizeFilename(t *testing.T) {
  function Test_getSessionId (line 49) | func Test_getSessionId(t *testing.T) {
  function Test_serveResult (line 59) | func Test_serveResult(t *testing.T) {

FILE: pkg/api/logger.go
  function init (line 21) | func init() {
  function SetLogger (line 28) | func SetLogger(l *logrus.Logger) {
  function RequestLogger (line 32) | func RequestLogger(logger *logrus.Logger) gin.HandlerFunc {
  function sanitizeLogPath (line 105) | func sanitizeLogPath(str string) string {
  function getRequestID (line 109) | func getRequestID(c *gin.Context) string {

FILE: pkg/api/logger_test.go
  function Test_getRequestID (line 11) | func Test_getRequestID(t *testing.T) {

FILE: pkg/api/middleware.go
  function dbCheckMiddleware (line 12) | func dbCheckMiddleware() gin.HandlerFunc {
  function corsMiddleware (line 52) | func corsMiddleware() gin.HandlerFunc {
  function requireLocalQueries (line 60) | func requireLocalQueries() gin.HandlerFunc {

FILE: pkg/api/routes.go
  function SetupMiddlewares (line 10) | func SetupMiddlewares(group *gin.RouterGroup) {
  function SetupRoutes (line 18) | func SetupRoutes(router *gin.Engine) {
  function SetupMetrics (line 63) | func SetupMetrics(engine *gin.Engine) {

FILE: pkg/api/session_manager.go
  type SessionManager (line 13) | type SessionManager struct
    method SetIdleTimeout (line 28) | func (m *SessionManager) SetIdleTimeout(timeout time.Duration) {
    method IDs (line 32) | func (m *SessionManager) IDs() []string {
    method Sessions (line 44) | func (m *SessionManager) Sessions() map[string]*client.Client {
    method Get (line 56) | func (m *SessionManager) Get(id string) *client.Client {
    method Add (line 63) | func (m *SessionManager) Add(id string, conn *client.Client) {
    method Remove (line 71) | func (m *SessionManager) Remove(id string) bool {
    method Len (line 85) | func (m *SessionManager) Len() int {
    method Cleanup (line 92) | func (m *SessionManager) Cleanup() int {
    method RunPeriodicCleanup (line 114) | func (m *SessionManager) RunPeriodicCleanup() {
    method staleSessions (line 122) | func (m *SessionManager) staleSessions() []string {
  function NewSessionManager (line 20) | func NewSessionManager(logger *logrus.Logger) *SessionManager {

FILE: pkg/api/session_manager_test.go
  function TestSessionManager (line 14) | func TestSessionManager(t *testing.T) {

FILE: pkg/api/types.go
  type localQuery (line 3) | type localQuery struct

FILE: pkg/bookmarks/bookmarks.go
  type Bookmark (line 11) | type Bookmark struct
    method SSHInfoIsEmpty (line 27) | func (b Bookmark) SSHInfoIsEmpty() bool {
    method ConvertToOptions (line 32) | func (b Bookmark) ConvertToOptions() command.Options {

FILE: pkg/bookmarks/bookmarks_test.go
  function TestBookmarkSSHInfoIsEmpty (line 11) | func TestBookmarkSSHInfoIsEmpty(t *testing.T) {
  function TestBookmarkWithVarsConvertToOptions (line 53) | func TestBookmarkWithVarsConvertToOptions(t *testing.T) {
  function TestBookmarkConvertToOptions (line 112) | func TestBookmarkConvertToOptions(t *testing.T) {

FILE: pkg/bookmarks/manager.go
  type Manager (line 13) | type Manager struct
    method Get (line 23) | func (m Manager) Get(id string) (*Bookmark, error) {
    method List (line 38) | func (m Manager) List() ([]Bookmark, error) {
    method ListIDs (line 42) | func (m Manager) ListIDs() ([]string, error) {
    method list (line 56) | func (m Manager) list() ([]Bookmark, error) {
  function NewManager (line 17) | func NewManager(dir string) Manager {
  function readBookmark (line 100) | func readBookmark(path string) (Bookmark, error) {
  function fileBasename (line 149) | func fileBasename(path string) string {

FILE: pkg/bookmarks/manager_test.go
  function TestManagerList (line 9) | func TestManagerList(t *testing.T) {
  function TestManagerListIDs (line 32) | func TestManagerListIDs(t *testing.T) {
  function TestManagerGet (line 43) | func TestManagerGet(t *testing.T) {
  function Test_fileBasename (line 55) | func Test_fileBasename(t *testing.T) {
  function Test_readBookmark (line 62) | func Test_readBookmark(t *testing.T) {

FILE: pkg/cli/cli.go
  function init (line 39) | func init() {
  function exitWithMessage (line 43) | func exitWithMessage(message string) {
  function initClientUsingBookmark (line 48) | func initClientUsingBookmark(baseDir, bookmarkName string) (*client.Clie...
  function initClient (line 58) | func initClient() {
  function initOptions (line 105) | func initOptions() {
  function configureLocalQueryStore (line 147) | func configureLocalQueryStore() {
  function configureLogger (line 170) | func configureLogger(opts command.Options) error {
  function printVersion (line 193) | func printVersion() {
  function startServer (line 197) | func startServer() {
  function startMetricsServer (line 227) | func startMetricsServer() {
  function handleSignals (line 239) | func handleSignals() {
  function openPage (line 245) | func openPage() {
  function testClient (line 267) | func testClient(cl *client.Client, retryCount int, retryDelay time.Durat...
  function Run (line 297) | func Run() {

FILE: pkg/client/client.go
  type Client (line 37) | type Client struct
    method init (line 182) | func (client *Client) init() {
    method setServerVersion (line 190) | func (client *Client) setServerVersion() {
    method Test (line 204) | func (client *Client) Test() error {
    method TestWithTimeout (line 229) | func (client *Client) TestWithTimeout(timeout time.Duration) (result e...
    method Info (line 255) | func (client *Client) Info() (*Result, error) {
    method Databases (line 267) | func (client *Client) Databases() ([]string, error) {
    method Schemas (line 271) | func (client *Client) Schemas() ([]string, error) {
    method Objects (line 275) | func (client *Client) Objects() (*Result, error) {
    method Table (line 279) | func (client *Client) Table(table string) (*Result, error) {
    method MaterializedView (line 284) | func (client *Client) MaterializedView(name string) (*Result, error) {
    method Function (line 288) | func (client *Client) Function(id string) (*Result, error) {
    method TableRows (line 292) | func (client *Client) TableRows(table string, opts RowsOptions) (*Resu...
    method EstimatedTableRowsCount (line 319) | func (client *Client) EstimatedTableRowsCount(table string, opts RowsO...
    method TableRowsCount (line 332) | func (client *Client) TableRowsCount(table string, opts RowsOptions) (...
    method TableInfo (line 355) | func (client *Client) TableInfo(table string) (*Result, error) {
    method TableIndexes (line 363) | func (client *Client) TableIndexes(table string) (*Result, error) {
    method TableConstraints (line 374) | func (client *Client) TableConstraints(table string) (*Result, error) {
    method TablesStats (line 385) | func (client *Client) TablesStats() (*Result, error) {
    method ServerSettings (line 389) | func (client *Client) ServerSettings() (*Result, error) {
    method Activity (line 394) | func (client *Client) Activity() (*Result, error) {
    method Query (line 408) | func (client *Client) Query(query string) (*Result, error) {
    method SetReadOnlyMode (line 419) | func (client *Client) SetReadOnlyMode() error {
    method ServerVersionInfo (line 433) | func (client *Client) ServerVersionInfo() string {
    method ServerVersion (line 437) | func (client *Client) ServerVersion() string {
    method context (line 441) | func (client *Client) context() (context.Context, context.CancelFunc) {
    method exec (line 448) | func (client *Client) exec(query string, args ...interface{}) (*Result...
    method query (line 481) | func (client *Client) query(query string, args ...interface{}) (*Resul...
    method Close (line 572) | func (client *Client) Close() error {
    method IsClosed (line 592) | func (c *Client) IsClosed() bool {
    method LastQueryTime (line 596) | func (c *Client) LastQueryTime() time.Time {
    method IsIdle (line 600) | func (client *Client) IsIdle() bool {
    method fetchRows (line 611) | func (client *Client) fetchRows(q string) ([]string, error) {
    method hasHistoryRecord (line 628) | func (client *Client) hasHistoryRecord(query string) bool {
    method GetConnContext (line 656) | func (client *Client) GetConnContext() (*ConnContext, error) {
  function getSchemaAndTable (line 51) | func getSchemaAndTable(str string) (string, string) {
  function New (line 59) | func New() (*Client, error) {
  function NewFromUrl (line 85) | func NewFromUrl(url string, sshInfo *shared.SSHInfo) (*Client, error) {
  function NewFromBookmark (line 149) | func NewFromBookmark(bookmark *bookmarks.Bookmark) (*Client, error) {
  type ConnContext (line 641) | type ConnContext struct
    method String (line 648) | func (c ConnContext) String() string {

FILE: pkg/client/client_test.go
  function mapKeys (line 30) | func mapKeys(data map[string]*Objects) []string {
  function objectNames (line 38) | func objectNames(data []Object) []string {
  function assertMatches (line 49) | func assertMatches(t *testing.T, expected, src []string) {
  function pgVersion (line 58) | func pgVersion() (int, int) {
  function getVar (line 66) | func getVar(name, def string) string {
  function initVars (line 74) | func initVars() {
  function setupCommands (line 87) | func setupCommands() {
  function onWindows (line 101) | func onWindows() bool {
  function setup (line 105) | func setup() {
  function setupClient (line 139) | func setupClient() {
  function teardownClient (line 144) | func teardownClient() {
  function teardown (line 150) | func teardown(t *testing.T, allowFail bool) {
  function testNewClientFromURL (line 169) | func testNewClientFromURL(t *testing.T) {
  function testClientIdleTime (line 189) | func testClientIdleTime(t *testing.T) {
  function testTest (line 204) | func testTest(t *testing.T) {
  function testInfo (line 247) | func testInfo(t *testing.T) {
  function testActivity (line 301) | func testActivity(t *testing.T) {
  function testDatabases (line 309) | func testDatabases(t *testing.T) {
  function testSchemas (line 315) | func testSchemas(t *testing.T) {
  function testObjects (line 321) | func testObjects(t *testing.T) {
  function testTable (line 395) | func testTable(t *testing.T) {
  function testTableRows (line 412) | func testTableRows(t *testing.T) {
  function testTableInfo (line 419) | func testTableInfo(t *testing.T) {
  function testEstimatedTableRowsCount (line 426) | func testEstimatedTableRowsCount(t *testing.T) {
  function testTableRowsCount (line 433) | func testTableRowsCount(t *testing.T) {
  function testTableRowsCountWithLargeTable (line 440) | func testTableRowsCountWithLargeTable(t *testing.T) {
  function testTableIndexes (line 450) | func testTableIndexes(t *testing.T) {
  function testTableConstraints (line 457) | func testTableConstraints(t *testing.T) {
  function testTableNameWithCamelCase (line 465) | func testTableNameWithCamelCase(t *testing.T) {
  function testQuery (line 488) | func testQuery(t *testing.T) {
  function testUpdateQuery (line 522) | func testUpdateQuery(t *testing.T) {
  function testTableRowsOrderEscape (line 565) | func testTableRowsOrderEscape(t *testing.T) {
  function testFunctions (line 576) | func testFunctions(t *testing.T) {
  function testResult (line 603) | func testResult(t *testing.T) {
  function testHistory (line 623) | func testHistory(t *testing.T) {
  function testReadOnlyMode (line 654) | func testReadOnlyMode(t *testing.T) {
  function testTablesStats (line 694) | func testTablesStats(t *testing.T) {
  function testConnContext (line 713) | func testConnContext(t *testing.T) {
  function testServerSettings (line 722) | func testServerSettings(t *testing.T) {
  function TestAll (line 748) | func TestAll(t *testing.T) {

FILE: pkg/client/codec.go
  constant CodecNone (line 12) | CodecNone   = "none"
  constant CodecHex (line 13) | CodecHex    = "hex"
  constant CodecBase58 (line 14) | CodecBase58 = "base58"
  constant CodecBase64 (line 15) | CodecBase64 = "base64"
  function SetBinaryCodec (line 23) | func SetBinaryCodec(codec string) error {
  function encodeBinaryData (line 34) | func encodeBinaryData(data []byte, codec string) string {

FILE: pkg/client/codec_test.go
  function TestSetBinaryCodec (line 10) | func TestSetBinaryCodec(t *testing.T) {
  function Test_encodeBinaryData (line 34) | func Test_encodeBinaryData(t *testing.T) {

FILE: pkg/client/dump.go
  type Dump (line 20) | type Dump struct
    method Validate (line 25) | func (d *Dump) Validate(serverVersion string) error {
    method Export (line 48) | func (d *Dump) Export(ctx context.Context, connstr string, writer io.W...
  function removeUnsupportedOptions (line 79) | func removeUnsupportedOptions(input string) (string, error) {

FILE: pkg/client/dump_test.go
  function testDumpExport (line 12) | func testDumpExport(t *testing.T) {

FILE: pkg/client/result.go
  constant ObjTypeTable (line 17) | ObjTypeTable            = "table"
  constant ObjTypeView (line 18) | ObjTypeView             = "view"
  constant ObjTypeMaterializedView (line 19) | ObjTypeMaterializedView = "materialized_view"
  constant ObjTypeSequence (line 20) | ObjTypeSequence         = "sequence"
  constant ObjTypeFunction (line 21) | ObjTypeFunction         = "function"
  type Row (line 26) | type Row
  type RowsOptions (line 29) | type RowsOptions struct
  type Pagination (line 37) | type Pagination struct
  type Result (line 44) | type Result struct
    method PostProcess (line 76) | func (res *Result) PostProcess() {
    method Format (line 116) | func (res *Result) Format() []map[string]interface{} {
    method CSV (line 131) | func (res *Result) CSV() []byte {
    method JSON (line 164) | func (res *Result) JSON() []byte {
  type ResultStats (line 51) | type ResultStats struct
  type Object (line 60) | type Object struct
  type Objects (line 65) | type Objects struct
  function ObjectsFromResult (line 176) | func ObjectsFromResult(res *Result) map[string]*Objects {

FILE: pkg/client/result_test.go
  function TestPostProcess (line 13) | func TestPostProcess(t *testing.T) {
  function TestCSV (line 53) | func TestCSV(t *testing.T) {
  function TestJSON (line 71) | func TestJSON(t *testing.T) {
  function TestResultFormat (line 124) | func TestResultFormat(t *testing.T) {

FILE: pkg/client/tunnel.go
  constant portStart (line 24) | portStart = 29168
  constant portLimit (line 25) | portLimit = 500
  type Tunnel (line 29) | type Tunnel struct
    method sshEndpoint (line 112) | func (tunnel *Tunnel) sshEndpoint() string {
    method targetEndpoint (line 116) | func (tunnel *Tunnel) targetEndpoint() string {
    method copy (line 120) | func (tunnel *Tunnel) copy(wg *sync.WaitGroup, writer, reader net.Conn) {
    method handleConnection (line 127) | func (tunnel *Tunnel) handleConnection(local net.Conn) {
    method Close (line 144) | func (tunnel *Tunnel) Close() {
    method Configure (line 155) | func (tunnel *Tunnel) Configure() error {
    method Start (line 178) | func (tunnel *Tunnel) Start() {
  function defaultKeyPath (line 39) | func defaultKeyPath() string {
  function expandKeyPath (line 43) | func expandKeyPath(path string) string {
  function fileExists (line 51) | func fileExists(path string) bool {
  function parsePrivateKey (line 56) | func parsePrivateKey(keyPath string, keyPass string) (ssh.Signer, error) {
  function makeConfig (line 73) | func makeConfig(info *shared.SSHInfo) (*ssh.ClientConfig, error) {
  function NewTunnel (line 192) | func NewTunnel(sshInfo *shared.SSHInfo, dbUrl string) (*Tunnel, error) {

FILE: pkg/client/util.go
  function getMajorMinorVersion (line 29) | func getMajorMinorVersion(str string) (major int, minor int) {
  function getMajorMinorVersionString (line 40) | func getMajorMinorVersionString(str string) string {
  function detectServerTypeAndVersion (line 45) | func detectServerTypeAndVersion(version string) (bool, string, string) {
  function detectDumpVersion (line 64) | func detectDumpVersion(version string) (bool, string) {
  function checkVersionRequirement (line 72) | func checkVersionRequirement(client, server string) bool {
  function containsRestrictedKeywords (line 84) | func containsRestrictedKeywords(str string) bool {
  function hasBinary (line 91) | func hasBinary(data string, checkLen int) bool {

FILE: pkg/client/util_test.go
  function TestDetectServerType (line 9) | func TestDetectServerType(t *testing.T) {
  function TestDetectDumpVersion (line 52) | func TestDetectDumpVersion(t *testing.T) {
  function TestGetMajorMinorVersion (line 74) | func TestGetMajorMinorVersion(t *testing.T) {
  function TestCheckVersionRequirement (line 98) | func TestCheckVersionRequirement(t *testing.T) {

FILE: pkg/command/options.go
  constant envVarPrefix (line 19) | envVarPrefix = "PGWEB_"
  type Options (line 22) | type Options struct
  function ParseOptions (line 74) | func ParseOptions(args []string) (Options, error) {
  function SetDefaultOptions (line 206) | func SetDefaultOptions() error {
  function getCurrentUser (line 216) | func getCurrentUser() string {
  function getPrefixedEnvVar (line 225) | func getPrefixedEnvVar(name string) string {
  function AvailableEnvVars (line 240) | func AvailableEnvVars() string {

FILE: pkg/command/options_test.go
  function TestParseOptions (line 12) | func TestParseOptions(t *testing.T) {

FILE: pkg/command/version.go
  constant Version (line 11) | Version = "0.17.0"
  type VersionInfo (line 31) | type VersionInfo struct
  function init (line 39) | func init() {
  function VersionString (line 47) | func VersionString() string {

FILE: pkg/connect/backend.go
  type Backend (line 14) | type Backend struct
    method SetLogger (line 30) | func (be *Backend) SetLogger(logger *logrus.Logger) {
    method SetPassHeaders (line 34) | func (be *Backend) SetPassHeaders(headers []string) {
    method FetchCredential (line 38) | func (be *Backend) FetchCredential(ctx context.Context, resource strin...
  function NewBackend (line 22) | func NewBackend(endpoint string, token string) Backend {

FILE: pkg/connect/backend_test.go
  function TestBackendFetchCredential (line 16) | func TestBackendFetchCredential(t *testing.T) {
  function startTestBackend (line 89) | func startTestBackend(ctx context.Context, listenAddr string) {
  function mustStartServer (line 145) | func mustStartServer(server *http.Server) {
  function waitForServer (line 158) | func waitForServer(addr string, n int) error {

FILE: pkg/connect/types.go
  type Request (line 11) | type Request struct
  type Credential (line 18) | type Credential struct

FILE: pkg/connection/connection_string.go
  function currentUser (line 24) | func currentUser() (string, error) {
  function hasValidPrefix (line 39) | func hasValidPrefix(str string) bool {
  function valsFromQuery (line 44) | func valsFromQuery(vals neturl.Values) map[string]string {
  function FormatURL (line 53) | func FormatURL(opts command.Options) (string, error) {
  function IsBlank (line 109) | func IsBlank(opts command.Options) bool {
  function BuildStringFromOptions (line 114) | func BuildStringFromOptions(opts command.Options) (string, error) {
  function lookupPassword (line 168) | func lookupPassword(opts command.Options, url *neturl.URL) string {

FILE: pkg/connection/connection_string_test.go
  function TestBuildStringFromOptions (line 13) | func TestBuildStringFromOptions(t *testing.T) {
  function TestFormatURL (line 217) | func TestFormatURL(t *testing.T) {
  function TestIsBlank (line 279) | func TestIsBlank(t *testing.T) {

FILE: pkg/connection/port.go
  function IsPortAvailable (line 11) | func IsPortAvailable(port int) bool {
  function FindAvailablePort (line 22) | func FindAvailablePort(start int, limit int) (int, error) {

FILE: pkg/connection/port_test.go
  function TestIsPortAvailable (line 13) | func TestIsPortAvailable(t *testing.T) {
  function TestFindAvailablePort (line 42) | func TestFindAvailablePort(t *testing.T) {

FILE: pkg/history/history.go
  type Record (line 7) | type Record struct
  function New (line 12) | func New() []Record {
  function NewRecord (line 16) | func NewRecord(query string) Record {

FILE: pkg/metrics/handler.go
  type Handler (line 10) | type Handler struct
    method ServeHTTP (line 15) | func (h Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
  function NewHandler (line 21) | func NewHandler() http.Handler {

FILE: pkg/metrics/metrics.go
  function init (line 37) | func init() {
  function IncrementQueriesCount (line 41) | func IncrementQueriesCount() {
  function SetSessionsCount (line 45) | func SetSessionsCount(val int) {
  function SetHealthy (line 49) | func SetHealthy(val bool) {

FILE: pkg/metrics/server.go
  function StartServer (line 9) | func StartServer(logger *logrus.Logger, path string, addr string) error {

FILE: pkg/queries/field.go
  type field (line 9) | type field struct
    method String (line 14) | func (f field) String() string {
    method matches (line 18) | func (f field) matches(input string) bool {
  function newField (line 25) | func newField(value string) (field, error) {

FILE: pkg/queries/field_test.go
  function Test_field (line 9) | func Test_field(t *testing.T) {
  function Test_fieldString (line 37) | func Test_fieldString(t *testing.T) {

FILE: pkg/queries/metadata.go
  type Metadata (line 21) | type Metadata struct
  function parseMetadata (line 31) | func parseMetadata(input string) (*Metadata, error) {
  function parseFields (line 100) | func parseFields(input string) (map[string]string, error) {
  function sanitizeMetadata (line 139) | func sanitizeMetadata(input string) string {

FILE: pkg/queries/metadata_test.go
  function Test_parseFields (line 9) | func Test_parseFields(t *testing.T) {
  function Test_parseMetadata (line 49) | func Test_parseMetadata(t *testing.T) {
  function Test_sanitizeMetadata (line 121) | func Test_sanitizeMetadata(t *testing.T) {

FILE: pkg/queries/query.go
  type Query (line 3) | type Query struct
    method IsPermitted (line 11) | func (q Query) IsPermitted(host, user, database, mode string) bool {

FILE: pkg/queries/query_test.go
  function TestQueryIsPermitted (line 9) | func TestQueryIsPermitted(t *testing.T) {
  function makeArgs (line 56) | func makeArgs(vals ...string) []string {
  function makeQuery (line 60) | func makeQuery(host, user, database, mode string) Query {

FILE: pkg/queries/store.go
  type Store (line 16) | type Store struct
    method Read (line 26) | func (s Store) Read(id string) (*Query, error) {
    method ReadAll (line 31) | func (s Store) ReadAll() ([]Query, error) {
  function NewStore (line 20) | func NewStore(dir string) *Store {
  function readQuery (line 64) | func readQuery(path string) (*Query, error) {

FILE: pkg/queries/store_test.go
  function TestStoreReadAll (line 11) | func TestStoreReadAll(t *testing.T) {
  function TestStoreRead (line 25) | func TestStoreRead(t *testing.T) {

FILE: pkg/shared/ssh_info.go
  type SSHInfo (line 8) | type SSHInfo struct
    method String (line 17) | func (info SSHInfo) String() string {

FILE: pkg/util/profiler.go
  constant MEGABYTE (line 10) | MEGABYTE = 1024 * 1024
  function runProfiler (line 12) | func runProfiler() {
  function StartProfiler (line 30) | func StartProfiler() {

FILE: static/data.go
  function GetFilesystem (line 13) | func GetFilesystem() http.FileSystem {
  function GetHandler (line 20) | func GetHandler() http.Handler {

FILE: static/js/ace-pgsql.js
  function a (line 1) | function a(){var e=o.replace("\\d","\\d\\-"),t={onMatch:function(e,t,n){...
  function f (line 1) | function f(e){return[{token:"comment",regex:/\/\*/,next:[i.getTagRule(),...

FILE: static/js/ace.js
  function o (line 1) | function o(n){var i=e;n&&(e[n]||(e[n]={}),i=e[n]);if(!i.define||!i.defin...
  function o (line 1) | function o(e){return(e.global?"g":"")+(e.ignoreCase?"i":"")+(e.multiline...
  function u (line 1) | function u(e,t,n){if(Array.prototype.indexOf)return e.indexOf(t,n);for(v...
  function r (line 1) | function r(){}
  function w (line 1) | function w(e){try{return Object.defineProperty(e,"sentinel",{}),"sentine...
  function H (line 1) | function H(e){return e=+e,e!==e?e=0:e!==0&&e!==1/0&&e!==-1/0&&(e=(e>0||-...
  function B (line 1) | function B(e){var t=typeof e;return e===null||t==="undefined"||t==="bool...
  function j (line 1) | function j(e){var t,n,r;if(B(e))return e;n=e.valueOf;if(typeof n=="funct...
  function e (line 1) | function e(e){var t=new Array(e+2);return t[0]=t[1]=0,t}
  function a (line 1) | function a(e,t,n){var a=u(t);if(!i.isMac&&s){t.getModifierState&&(t.getM...
  function f (line 1) | function f(){s=Object.create(null)}
  function i (line 1) | function i(e){n&&n(e),r&&r(e),t.removeListener(document,"mousemove",n,!0...
  function c (line 1) | function c(e){t.getButton(e)!==0?o=0:e.detail>1?(o++,o>4&&(o=1)):o=1;if(...
  function h (line 1) | function h(e){o=2,f&&clearTimeout(f),f=setTimeout(function(){f=null},n[o...
  function z (line 1) | function z(){S=!0,n.blur(),n.focus(),S=!1}
  function X (line 1) | function X(e){e.keyCode==27&&n.value.length<n.selectionStart&&(y||(x=n.v...
  function $ (line 1) | function $(){clearTimeout(V),V=setTimeout(function(){w&&(n.style.cssText...
  function K (line 1) | function K(e,t,n){var r=null,i=!1;n.addEventListener("keydown",function(...
  function o (line 1) | function o(e){e.$clickSelection=null;var t=e.editor;t.setDefaultHandler(...
  function u (line 1) | function u(e,t,n,r){return Math.sqrt(Math.pow(n-e,2)+Math.pow(r-t,2))}
  function a (line 1) | function a(e,t){if(e.start.row==e.end.row)var n=2*t.column-e.start.colum...
  function s (line 1) | function s(e){this.isOpen=!1,this.$element=null,this.$parentNode=e}
  function u (line 1) | function u(e){function l(){var r=u.getDocumentPosition().row,s=n.$annota...
  function a (line 1) | function a(e){o.call(this,e)}
  function f (line 1) | function f(e){function T(e,n){var r=Date.now(),i=!n||e.row!=n.row,s=!n||...
  function l (line 1) | function l(e,t,n,r){return Math.sqrt(Math.pow(n-e,2)+Math.pow(r-t,2))}
  function y (line 1) | function y(){var e=window.navigator&&window.navigator.clipboard,r=!1,s=f...
  function b (line 1) | function b(){g||y();var e=t.selection.cursor,n=t.renderer.textToScreenCo...
  function w (line 1) | function w(e){g&&(g.style.display="none"),t.off("input",w)}
  function E (line 1) | function E(){f=null,clearTimeout(f);var e=t.selection.getRange(),r=e.con...
  function S (line 1) | function S(){f=null,clearTimeout(f),t.selection.moveToPosition(h);var e=...
  function x (line 1) | function x(){c+=60,l=setInterval(function(){c--<=0&&(clearInterval(l),l=...
  function o (line 1) | function o(e){typeof console!="undefined"&&console.warn&&console.warn.ap...
  function u (line 1) | function u(e,t){var n=new Error(e);n.data=t,typeof console=="object"&&co...
  function l (line 1) | function l(r){if(!u||!u.document)return;a.packaged=r||e.packaged||n.pack...
  function c (line 1) | function c(e){return e.replace(/-(.)/g,function(e,t){return t.toUpperCas...
  function i (line 1) | function i(e){e.on("click",function(t){var n=t.getDocumentPosition(),i=e...
  function F (line 1) | function F(e,t,n,r){var i=s?d:p,c=null,h=null,v=null,m=0,g=null,y=null,b...
  function I (line 1) | function I(e,t,n){if(o<e)return;if(e==1&&s==m&&!f){n.reverse();return}va...
  function q (line 1) | function q(e,t,n,r){var i=t[r],o,c,h,p;switch(i){case g:case y:u=!1;case...
  function R (line 1) | function R(e){var t=e.charCodeAt(0),n=t>>8;return n==0?t>191?g:B[t]:n==5...
  function U (line 1) | function U(e){return e>="\u064b"&&e<="\u0655"}
  function i (line 1) | function i(s){var o=r[s];o.processed=!0;for(var u=0;u<o.length;u++){var ...
  function w (line 1) | function w(e){for(var t=n;t<=r;t++)e(i.getLine(t),t)}
  function r (line 1) | function r(e,t){throw console.log("Invalid Delta:",e),"Invalid Delta: "+t}
  function i (line 1) | function i(e,t){return t.row>=0&&t.row<e.length&&t.column>=0&&t.column<=...
  function s (line 1) | function s(e,t){t.action!="insert"&&t.action!="remove"&&r(t,"delta.actio...
  function e (line 1) | function e(e,t,n){var r=n?e.column<=t.column:e.column<t.column;return e....
  function t (line 1) | function t(t,n,r){var i=t.action=="insert",s=(i?1:-1)*(t.end.row-t.start...
  function i (line 1) | function i(e,t){this.foldData=e,Array.isArray(t)?this.folds=t:t=this.fol...
  function o (line 1) | function o(e,t){e.row-=t.row,e.row==0&&(e.column-=t.column)}
  function u (line 1) | function u(e,t){o(e.start,t),o(e.end,t)}
  function a (line 1) | function a(e,t){e.row==0&&(e.column+=t.column),e.row+=t.row}
  function f (line 1) | function f(e,t){a(e.start,t),a(e.end,t)}
  function u (line 1) | function u(){this.getFoldAt=function(e,t,n){var r=this.getFoldLine(e);if...
  function s (line 1) | function s(){this.findMatchingBracket=function(e,t){if(e.column==0)retur...
  function m (line 1) | function m(e){return e<4352?!1:e>=4352&&e<=4447||e>=4515&&e<=4519||e>=46...
  function n (line 1) | function n(e){return t?e.action!=="insert":e.action==="insert"}
  function g (line 1) | function g(){var t=0;if(m===0)return t;if(p)for(var n=0;n<e.length;n++){...
  function y (line 1) | function y(t){var n=t-f;for(var r=f;r<t;r++){var i=e[r];if(i===12||i===2...
  function u (line 1) | function u(e,t){function n(e){return/\w/.test(e)||t.regExp?"\\b":""}retu...
  function o (line 1) | function o(e,t){this.platform=t||(i.isMac?"mac":"win"),this.commands={},...
  function u (line 1) | function u(e,t){o.call(this,e,t),this.$singleCommand=!1}
  function e (line 1) | function e(e){return typeof e=="object"&&e.bindKey&&e.bindKey.position||...
  function o (line 1) | function o(e,t){return{win:e,mac:t}}
  function i (line 1) | function i(e,t){for(var n=t;n--;){var r=e[n];if(r&&!r[0].ignore){while(n...
  function a (line 1) | function a(e){var t=e.action=="insert",n=e.start,r=e.end,i=(r.row-n.row)...
  function f (line 1) | function f(e){return{row:e.row,column:e.column}}
  function l (line 1) | function l(e){return{start:f(e.start),end:f(e.end),action:e.action,lines...
  function c (line 1) | function c(e){e=e||this;if(Array.isArray(e))return e.map(c).join("\n");v...
  function h (line 1) | function h(e){return e.start.row+":"+e.start.column+"=>"+e.end.row+":"+e...
  function p (line 1) | function p(e,t){var n=e.action=="insert",r=t.action=="insert";if(n&&r)if...
  function d (line 1) | function d(e,t){for(var n=e.length;n--;)for(var r=0;r<t.length;r++)if(!p...
  function v (line 1) | function v(e,t){var n=e.action=="insert",r=t.action=="insert";if(n&&r)o(...
  function m (line 1) | function m(e,t,n){g(e.start,t.start,t.end,n),g(e.end,t.start,t.end,n)}
  function g (line 1) | function g(e,t,n,r){e.row==(r==1?t:n).row&&(e.column+=r*(n.column-t.colu...
  function y (line 1) | function y(e,t){var n=e.lines,r=e.end;e.end=f(t);var i=e.end.row-e.start...
  function b (line 1) | function b(e,t){t=l(t);for(var n=e.length;n--;){var r=e[n];for(var i=0;i...
  function w (line 1) | function w(e,t){for(var n=0;n<t.length;n++){var r=t[n];for(var i=0;i<r.l...
  function f (line 1) | function f(e){var t=document.createTextNode("");e.appendChild(t);var n=r...
  function e (line 1) | function e(e,t,n,r){return(e?1:0)|(t?2:0)|(n?4:0)|(r?8:0)}
  function i (line 1) | function i(e,t,n){var i=0,s=0;while(s+e[i].value.length<t){s+=e[i].value...
  function r (line 1) | function r(e,t,n){var r=e[1]*t[0]-e[0]*t[1];return[(-t[1]*n[0]+t[0]*n[1]...
  function i (line 1) | function i(e,t){return[e[0]-t[0],e[1]-t[1]]}
  function s (line 1) | function s(e,t){return[e[0]+t[0],e[1]+t[1]]}
  function o (line 1) | function o(e,t){return[e*t[0],e*t[1]]}
  function u (line 1) | function u(e){var t=e.getBoundingClientRect();return[t.left,t.top]}
  function o (line 1) | function o(r){if(n.$themeId!=e)return t&&t();if(!r||!r.cssClass)throw ne...
  function u (line 1) | function u(e){var t="importScripts('"+i.qualifyURL(e)+"');";try{return n...
  function a (line 1) | function a(e){if(typeof Worker=="undefined")return{postMessage:function(...
  function s (line 1) | function s(e,t){return e.row==t.row&&e.column==t.column}
  function o (line 1) | function o(e){var t=e.domEvent,n=t.altKey,o=t.shiftKey,u=t.ctrlKey,a=e.g...
  function h (line 1) | function h(e,t,n){return c.$options.wrap=!0,c.$options.needle=t,c.$optio...
  function v (line 1) | function v(e,t){return e.row==t.row&&e.column==t.column}
  function m (line 1) | function m(e){if(e.$multiselectOnSessionChange)return;e.$onAddRange=e.$o...
  function g (line 1) | function g(e){function r(t){n&&(e.renderer.setMouseCursor(""),n=!1)}if(!...
  function u (line 1) | function u(e){return a.stringRepeat(" ",e)}
  function f (line 1) | function f(e){return e[2]?u(i)+e[2]+u(s-e[2].length+o)+e[4].replace(/^([...
  function l (line 1) | function l(e){return e[2]?u(i+s-e[2].length)+e[2]+u(o)+e[4].replace(/^([...
  function c (line 1) | function c(e){return e[2]?u(i)+e[2]+u(o)+e[4].replace(/^([=:])\s+/,"$1 "...
  function o (line 1) | function o(e){this.session=e,this.session.widgetManager=this,this.sessio...
  function o (line 1) | function o(e,t,n){var r=0,i=e.length-1;while(r<=i){var s=r+i>>1,o=n(t,e[...
  function u (line 1) | function u(e,t,n){var r=e.getAnnotations().sort(s.comparePoints);if(!r.l...

FILE: static/js/app.js
  function getSessionId (line 25) | function getSessionId() {
  function setRowsLimit (line 36) | function setRowsLimit(num) {
  function getRowsLimit (line 40) | function getRowsLimit() {
  function getPaginationOffset (line 44) | function getPaginationOffset() {
  function getPagesCount (line 50) | function getPagesCount(rowsCount) {
  function apiCall (line 61) | function apiCall(method, path, params, cb) {
  function getInfo (line 100) | function getInfo(cb)                        { apiCall("get", "/info", {}...
  function getConnection (line 101) | function getConnection(cb)                  { apiCall("get", "/connectio...
  function getServerSettings (line 102) | function getServerSettings(cb)              { apiCall("get", "/server_se...
  function getSchemas (line 103) | function getSchemas(cb)                     { apiCall("get", "/schemas",...
  function getObjects (line 104) | function getObjects(cb)                     { apiCall("get", "/objects",...
  function getTables (line 105) | function getTables(cb)                      { apiCall("get", "/tables", ...
  function getTableRows (line 106) | function getTableRows(table, opts, cb)      { apiCall("get", "/tables/" ...
  function getTableStructure (line 107) | function getTableStructure(table, opts, cb) { apiCall("get", "/tables/" ...
  function getTableIndexes (line 108) | function getTableIndexes(table, cb)         { apiCall("get", "/tables/" ...
  function getTableConstraints (line 109) | function getTableConstraints(table, cb)     { apiCall("get", "/tables/" ...
  function getTablesStats (line 110) | function getTablesStats(cb)                 { apiCall("get", "/tables_st...
  function getFunction (line 111) | function getFunction(id, cb)                { apiCall("get", "/functions...
  function getHistory (line 112) | function getHistory(cb)                     { apiCall("get", "/history",...
  function getBookmarks (line 113) | function getBookmarks(cb)                   { apiCall("get", "/bookmarks...
  function executeQuery (line 114) | function executeQuery(query, cb)            { apiCall("post", "/query", ...
  function explainQuery (line 115) | function explainQuery(query, cb)            { apiCall("post", "/explain"...
  function analyzeQuery (line 116) | function analyzeQuery(query, cb)            { apiCall("post", "/analyze"...
  function disconnect (line 117) | function disconnect(cb)                     { apiCall("post", "/disconne...
  function encodeQuery (line 119) | function encodeQuery(query) {
  function showErrorBanner (line 123) | function showErrorBanner(text) {
  function buildSchemaSection (line 135) | function buildSchemaSection(name, objects) {
  function loadLocalQueries (line 189) | function loadLocalQueries() {
  function loadSchemas (line 216) | function loadSchemas() {
  function escapeHtml (line 281) | function escapeHtml(str) {
  function unescapeHtml (line 289) | function unescapeHtml(str){
  function getCurrentObject (line 295) | function getCurrentObject() {
  function resetTable (line 299) | function resetTable() {
  function performTableAction (line 311) | function performTableAction(table, action, el) {
  function performViewAction (line 353) | function performViewAction(view, action, el) {
  function performRowAction (line 398) | function performRowAction(action, value) {
  function sortArrow (line 408) | function sortArrow(direction) {
  function buildTable (line 419) | function buildTable(results, sortColumn, sortOrder, options) {
  function setCurrentTab (line 489) | function setCurrentTab(id) {
  function showQueryHistory (line 502) | function showQueryHistory() {
  function showTableIndexes (line 519) | function showTableIndexes() {
  function showTableConstraints (line 537) | function showTableConstraints() {
  function showTableInfo (line 555) | function showTableInfo() {
  function updatePaginator (line 575) | function updatePaginator(pagination) {
  function showTableContent (line 606) | function showTableContent(sortColumn, sortOrder) {
  function showPaginatedTableContent (line 654) | function showPaginatedTableContent() {
  function showDatabaseStats (line 667) | function showDatabaseStats() {
  function downloadDatabaseStats (line 678) | function downloadDatabaseStats() {
  function showServerSettings (line 682) | function showServerSettings() {
  function showTableStructure (line 693) | function showTableStructure() {
  function showViewDefinition (line 719) | function showViewDefinition(viewName, viewDefintion) {
  function showFunctionDefinition (line 724) | function showFunctionDefinition(functionName, definition) {
  function renderResultsView (line 729) | function renderResultsView(title, content) {
  function showQueryPanel (line 749) | function showQueryPanel() {
  function showConnectionPanel (line 761) | function showConnectionPanel() {
  function showActivityPanel (line 780) | function showActivityPanel() {
  function showQueryProgressMessage (line 799) | function showQueryProgressMessage() {
  function hideQueryProgressMessage (line 805) | function hideQueryProgressMessage() {
  function getEditorSelection (line 810) | function getEditorSelection() {
  function getSubquery (line 839) | function getSubquery(text, cursor) {
  function runQuery (line 876) | function runQuery() {
  function runExplain (line 905) | function runExplain() {
  function runAnalyze (line 925) | function runAnalyze() {
  function generateURL (line 945) | function generateURL(path, params) {
  function openInNewWindow (line 959) | function openInNewWindow(path, params) {
  function exportTo (line 965) | function exportTo(format) {
  function showUniqueColumnsValues (line 980) | function showUniqueColumnsValues(table, column, showCounts) {
  function showFieldNumStats (line 998) | function showFieldNumStats(table, column) {
  function buildTableFilters (line 1009) | function buildTableFilters(name, type) {
  function initEditor (line 1035) | function initEditor() {
  function addShortcutTooltips (line 1089) | function addShortcutTooltips() {
  function getLatestReleaseInfo (line 1101) | function getLatestReleaseInfo(current) {
  function showConnectionSettings (line 1115) | function showConnectionSettings() {
  function initConnectionWindow (line 1158) | function initConnectionWindow() {
  function getConnectionString (line 1174) | function getConnectionString() {
  function bindTableHeaderMenu (line 1204) | function bindTableHeaderMenu() {
  function bindCurrentDatabaseMenu (line 1282) | function bindCurrentDatabaseMenu() {
  function bindDatabaseObjectsFilter (line 1306) | function bindDatabaseObjectsFilter() {
  function resetObjectsFilter (line 1333) | function resetObjectsFilter() {
  function filterObjectsByName (line 1339) | function filterObjectsByName(query) {
  function getQuotedSchemaTableName (line 1352) | function getQuotedSchemaTableName(table) {
  function bindContextMenus (line 1360) | function bindContextMenus() {
  function toggleDatabaseSearch (line 1408) | function toggleDatabaseSearch() {
  function enableDatabaseSearch (line 1413) | function enableDatabaseSearch(data) {
  function bindInputResizeEvents (line 1434) | function bindInputResizeEvents() {
  function checkInputSize (line 1447) | function checkInputSize() {
  function resizeInput (line 1458) | function resizeInput(height) {
  function beginInputResize (line 1473) | function beginInputResize() {
  function endInputResize (line 1481) | function endInputResize() {
  function onInputResize (line 1494) | function onInputResize(event) {
  function bindContentModalEvents (line 1503) | function bindContentModalEvents() {

FILE: static/js/bootstrap-dropdown.js
  function clearMenus (line 86) | function clearMenus(e) {
  function getParent (line 99) | function getParent($this) {
  function Plugin (line 116) | function Plugin(option) {

FILE: static/js/ext-language_tools.js
  function h (line 1) | function h(e){var t=(new Date).toLocaleString("en-us",e);return t.length...
  function e (line 1) | function e(e){return e=e.substr(1),/^\d+$/.test(e)?[{tabstopId:parseInt(...
  function t (line 1) | function t(e){return"(?:[^\\\\"+e+"]|\\\\.)"}
  function f (line 1) | function f(t){var n=e.indexOf(t,s+1);n!=-1&&(s=n)}
  function f (line 1) | function f(e){var t=[];for(var n=0;n<e.length;n++){var r=e[n];if(typeof ...
  function o (line 1) | function o(e){return e&&!/^\^?\(.*\)\$?$|^\\b$/.test(e)&&(e="(?:"+e+")")...
  function u (line 1) | function u(e,t,n){return e=o(e),t=o(t),n?(e=t+e,e&&e[e.length-1]!="$"&&(...
  function a (line 1) | function a(e){e.scope||(e.scope=t||"_"),t=e.scope,n[t]||(n[t]=[],r[t]={}...
  function i (line 1) | function i(e){var i=r[e.scope||t];if(i&&i[e.name]){delete i[e.name];var ...
  function s (line 1) | function s(e,n){e&&r.push({type:(t.className||"")+(n||""),value:e})}
  function s (line 1) | function s(e,t){var n=e.getTextRange(r.fromPoints({row:0,column:0},t));r...
  function o (line 1) | function o(e,t){var n=s(e,t),r=e.getValue().split(i),o=Object.create(nul...

FILE: static/js/jquery.js
  function s (line 2) | function s(a){var b=a.length,c=n.type(a);return"function"===c||n.isWindo...
  function fb (line 2) | function fb(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)...
  function gb (line 2) | function gb(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLengt...
  function hb (line 2) | function hb(a){return a[u]=!0,a}
  function ib (line 2) | function ib(a){var b=n.createElement("div");try{return!!a(b)}catch(c){re...
  function jb (line 2) | function jb(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[...
  function kb (line 2) | function kb(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sou...
  function lb (line 2) | function lb(a){return function(b){var c=b.nodeName.toLowerCase();return"...
  function mb (line 2) | function mb(a){return function(b){var c=b.nodeName.toLowerCase();return(...
  function nb (line 2) | function nb(a){return hb(function(b){return b=+b,hb(function(c,d){var e,...
  function ob (line 2) | function ob(a){return a&&typeof a.getElementsByTagName!==C&&a}
  function pb (line 2) | function pb(){}
  function qb (line 2) | function qb(a){for(var b=0,c=a.length,d="";c>b;b++)d+=a[b].value;return d}
  function rb (line 2) | function rb(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.firs...
  function sb (line 2) | function sb(a){return a.length>1?function(b,c,d){var e=a.length;while(e-...
  function tb (line 2) | function tb(a,b,c){for(var d=0,e=b.length;e>d;d++)fb(a,b[d],c);return c}
  function ub (line 2) | function ub(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(...
  function vb (line 2) | function vb(a,b,c,d,e,f){return d&&!d[u]&&(d=vb(d)),e&&!e[u]&&(e=vb(e,f)...
  function wb (line 2) | function wb(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.r...
  function xb (line 2) | function xb(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var...
  function x (line 2) | function x(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){retur...
  function D (line 2) | function D(a,b){while((a=a[b])&&1!==a.nodeType);return a}
  function G (line 2) | function G(a){var b=F[a]={};return n.each(a.match(E)||[],function(a,c){b...
  function I (line 2) | function I(){l.removeEventListener("DOMContentLoaded",I,!1),a.removeEven...
  function K (line 2) | function K(){Object.defineProperty(this.cache={},0,{get:function(){retur...
  function P (line 2) | function P(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.rep...
  function Z (line 3) | function Z(){return!0}
  function $ (line 3) | function $(){return!1}
  function _ (line 3) | function _(){try{return l.activeElement}catch(a){}}
  function jb (line 3) | function jb(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeTyp...
  function kb (line 3) | function kb(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}
  function lb (line 3) | function lb(a){var b=gb.exec(a.type);return b?a.type=b[1]:a.removeAttrib...
  function mb (line 3) | function mb(a,b){for(var c=0,d=a.length;d>c;c++)L.set(a[c],"globalEval",...
  function nb (line 3) | function nb(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(L.hasData(a)&...
  function ob (line 3) | function ob(a,b){var c=a.getElementsByTagName?a.getElementsByTagName(b||...
  function pb (line 3) | function pb(a,b){var c=b.nodeName.toLowerCase();"input"===c&&T.test(a.ty...
  function sb (line 3) | function sb(b,c){var d,e=n(c.createElement(b)).appendTo(c.body),f=a.getD...
  function tb (line 3) | function tb(a){var b=l,c=rb[a];return c||(c=sb(a,b),"none"!==c&&c||(qb=(...
  function xb (line 3) | function xb(a,b,c){var d,e,f,g,h=a.style;return c=c||wb(a),c&&(g=c.getPr...
  function yb (line 3) | function yb(a,b){return{get:function(){return a()?void delete this.get:(...
  function g (line 3) | function g(){f.style.cssText="-webkit-box-sizing:border-box;-moz-box-siz...
  function Fb (line 3) | function Fb(a,b){if(b in a)return b;var c=b[0].toUpperCase()+b.slice(1),...
  function Gb (line 3) | function Gb(a,b,c){var d=Ab.exec(b);return d?Math.max(0,d[1]-(c||0))+(d[...
  function Hb (line 3) | function Hb(a,b,c,d,e){for(var f=c===(d?"border":"content")?4:"width"===...
  function Ib (line 3) | function Ib(a,b,c){var d=!0,e="width"===b?a.offsetWidth:a.offsetHeight,f...
  function Jb (line 3) | function Jb(a,b){for(var c,d,e,f=[],g=0,h=a.length;h>g;g++)d=a[g],d.styl...
  function Kb (line 3) | function Kb(a,b,c,d,e){return new Kb.prototype.init(a,b,c,d,e)}
  function Sb (line 3) | function Sb(){return setTimeout(function(){Lb=void 0}),Lb=n.now()}
  function Tb (line 3) | function Tb(a,b){var c,d=0,e={height:a};for(b=b?1:0;4>d;d+=2-b)c=R[d],e[...
  function Ub (line 3) | function Ub(a,b,c){for(var d,e=(Rb[b]||[]).concat(Rb["*"]),f=0,g=e.lengt...
  function Vb (line 3) | function Vb(a,b,c){var d,e,f,g,h,i,j,k,l=this,m={},o=a.style,p=a.nodeTyp...
  function Wb (line 3) | function Wb(a,b){var c,d,e,f,g;for(c in a)if(d=n.camelCase(c),e=b[d],f=a...
  function Xb (line 3) | function Xb(a,b,c){var d,e,f=0,g=Qb.length,h=n.Deferred().always(functio...
  function rc (line 4) | function rc(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var ...
  function sc (line 4) | function sc(a,b,c,d){var e={},f=a===oc;function g(h){var i;return e[h]=!...
  function tc (line 4) | function tc(a,b){var c,d,e=n.ajaxSettings.flatOptions||{};for(c in b)voi...
  function uc (line 4) | function uc(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[...
  function vc (line 4) | function vc(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])fo...
  function x (line 4) | function x(a,b,f,h){var j,r,s,u,w,x=b;2!==t&&(t=2,g&&clearTimeout(g),c=v...
  function Bc (line 4) | function Bc(a,b,c,d){var e;if(n.isArray(b))n.each(b,function(b,e){c||xc....
  function Kc (line 4) | function Kc(a){return n.isWindow(a)?a:9===a.nodeType&&a.defaultView}

FILE: static/js/utils.js
  function copyToClipboard (line 11) | function copyToClipboard(text) {
  function guid (line 24) | function guid() {
Condensed preview — 135 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,130K chars).
[
  {
    "path": ".claude/settings.local.json",
    "chars": 194,
    "preview": "{\n  \"permissions\": {\n    \"allow\": [\n      \"Bash(grep:*)\",\n      \"Bash(find:*)\",\n      \"Bash(git checkout:*)\",\n      \"Bas"
  },
  {
    "path": ".dockerignore",
    "chars": 21,
    "preview": ".github\nbin/\n./pgweb\n"
  },
  {
    "path": ".gitattributes",
    "chars": 17,
    "preview": "bindata.go -diff\n"
  },
  {
    "path": ".github/workflows/checks.yml",
    "chars": 2662,
    "preview": "name: checks\n\non:\n  push:\n    branches:\n      - main\n    paths-ignore:\n      - '**.md'\n  pull_request:\n    types:\n      "
  },
  {
    "path": ".github/workflows/deploy.yml",
    "chars": 343,
    "preview": "name: demo deploy\n\non:\n  push:\n    branches:\n      - main\n\nenv:\n  FLY_API_TOKEN: ${{ secrets.FLY_TOKEN }}\n\njobs:\n  deplo"
  },
  {
    "path": ".github/workflows/docker.yml",
    "chars": 757,
    "preview": "name: docker\n\non:\n  push:\n    branches:\n      - main\n\nenv:\n  GO_VERSION: \"1.25\"\n  CGO_ENABLED: 0\n  IMAGE_REPOSITORY: sos"
  },
  {
    "path": ".github/workflows/release.yml",
    "chars": 1667,
    "preview": "name: release\n\non:\n  push:\n    tags:\n      - \"v*\"\n\nenv:\n  GO_VERSION: \"1.25\"\n  CGO_ENABLED: 0\n  DOCKER_REPOSITORY: sosed"
  },
  {
    "path": ".gitignore",
    "chars": 53,
    "preview": ".DS_Store\n.idea\n.env\n.envrc\npgweb\nbin\ntmp/\ncover.out\n"
  },
  {
    "path": ".golangci.yml",
    "chars": 187,
    "preview": "version: \"2\"\nlinters:\n  disable:\n    - errcheck\n  settings:\n    staticcheck:\n      checks: [\"all\", \"-ST1000\", \"-ST1003\","
  },
  {
    "path": "CHANGELOG.md",
    "chars": 19502,
    "preview": "## Changelog\n\nCurrent [release](https://github.com/sosedoff/pgweb/releases) is `0.17.0`.\n\n## Next\n\n- `NEW` Add PGWEB_BOO"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 272,
    "preview": "- Fork repository\n- Create a new git branch\n- Make changes\n- Run tests: `make test`\n- Run tests against all supported Po"
  },
  {
    "path": "Dockerfile",
    "chars": 1734,
    "preview": "# ------------------------------------------------------------------------------\n# Builder Stage\n# ---------------------"
  },
  {
    "path": "LICENSE",
    "chars": 1109,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2014-2024 Dan Sosedoff <dan.sosedoff@gmail.com>\n\nPermission is hereby granted, free"
  },
  {
    "path": "Makefile",
    "chars": 2119,
    "preview": "PKG = github.com/sosedoff/pgweb\nGIT_COMMIT ?= $(shell git rev-parse --short=8 HEAD)\nBUILD_TIME ?= $(shell date -u +\"%Y-%"
  },
  {
    "path": "Procfile",
    "chars": 107,
    "preview": "web: pgweb --url=$DATABASE_URL --listen=$PORT --bind=0.0.0.0 --auth-user=$AUTH_USER --auth-pass=$AUTH_PASS\n"
  },
  {
    "path": "README.md",
    "chars": 3129,
    "preview": "# pgweb\n\nSimple web-based and cross platform PostgreSQL database explorer.\n\n[![Release](https://img.shields.io/github/re"
  },
  {
    "path": "SCREENS.md",
    "chars": 183,
    "preview": "# Screenshots\n\n### Connect\n<img src=\"screenshots/connect.png\" />\n\n### Browse table rows\n<img src=\"screenshots/browse.png"
  },
  {
    "path": "config/examples/connect_backend_go/README.md",
    "chars": 236,
    "preview": "# connect-backend-go\n\nExample Golang backend for Pgweb Connect feature\n\n## Usage\n\nRun the backend:\n\n```bash\ngo run main."
  },
  {
    "path": "config/examples/connect_backend_go/main.go",
    "chars": 1077,
    "preview": "package main\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"log\"\n\t\"net/http\"\n)\n\ntype BackendRequest struct {\n\tResource string      "
  },
  {
    "path": "config/examples/connect_backend_ruby/Gemfile",
    "chars": 80,
    "preview": "source \"https://rubygems.org\"\n\ngem \"sinatra\"\ngem \"json\"\ngem \"puma\"\ngem \"rackup\"\n"
  },
  {
    "path": "config/examples/connect_backend_ruby/README.md",
    "chars": 261,
    "preview": "# connect-backend-ruby\n\nExample Ruby backend for Pgweb Connect feature\n\n## Usage\n\nInstall and run the backend:\n\n```bash\n"
  },
  {
    "path": "config/examples/connect_backend_ruby/config.ru",
    "chars": 67,
    "preview": "require \"bundler/setup\"\nrequire \"./main\"\n\nrun Sinatra::Application\n"
  },
  {
    "path": "config/examples/connect_backend_ruby/main.rb",
    "chars": 765,
    "preview": "require \"bundler/setup\"\nrequire \"sinatra\"\n\n# Authentication token\n$token = \"test\"\n\n# List of all available resources\n$re"
  },
  {
    "path": "config/pgweb.freebsd_rc",
    "chars": 1369,
    "preview": "#!/bin/sh\n#\n# $FreeBSD: $\n#\n# PROVIDE: pgweb\n# REQUIRE: NETWORKING\n# KEYWORD:\n#\n# Add the following lines to /etc/rc.con"
  },
  {
    "path": "config/pgweb.service",
    "chars": 227,
    "preview": "[Unit]\nDescription=pgweb - Cross-platform client for PostgreSQL databases\nAfter=network.target\n\n[Service]\nType=simple\nEx"
  },
  {
    "path": "config/pgweb_initd.conf",
    "chars": 1578,
    "preview": "#!/bin/sh\n### BEGIN INIT INFO\n# Provides:          pgweb\n# Required-Start:    $remote_fs $syslog\n# Required-Stop:     $r"
  },
  {
    "path": "config/pgweb_upstart.conf",
    "chars": 205,
    "preview": "description \"PgWeb as a Service for Ubuntu 14.04 With Upstart\"\n\nstart on runlevel [2345]\nstop on runlevel [!2345]\n\nrespa"
  },
  {
    "path": "data/bookmark.toml",
    "chars": 89,
    "preview": "host = \"localhost\"\nport = 5432\nuser = \"postgres\"\ndatabase = \"mydatabase\"\nssl = \"disable\"\n"
  },
  {
    "path": "data/bookmark_invalid_ssl.toml",
    "chars": 90,
    "preview": "host = \"localhost\"\nport = 5432\nuser = \"postgres\"\ndatabase = \"mydatabase\"\nssl = \"disabled\"\n"
  },
  {
    "path": "data/bookmark_url.toml",
    "chars": 72,
    "preview": "url = \"postgres://username:password@host:port/database?sslmode=disable\"\n"
  },
  {
    "path": "data/bookmark_with_ssh.toml",
    "chars": 218,
    "preview": "host = \"localhost\"\nport = 5432\nuser = \"postgres\"\ndatabase = \"mydatabase\"\nssl = \"disable\"\n\n[SSH]\nhost = \"ssh-host\"\nuser ="
  },
  {
    "path": "data/booktown.sql",
    "chars": 43171,
    "preview": "--\n-- Selected TOC Entries:\n--\n--\n-- TOC Entry ID 1 (OID 0)\n--\n-- Name: booktown Type: DATABASE Owner: postgres\n--\n\nDROP"
  },
  {
    "path": "data/invalid.toml",
    "chars": 16,
    "preview": "invalid encoding"
  },
  {
    "path": "data/lc_example1.sql",
    "chars": 40,
    "preview": "-- pgweb: host=\"localhost\"\nselect 'foo'\n"
  },
  {
    "path": "data/lc_example2.sql",
    "chars": 78,
    "preview": "-- pgweb: host=\"localhost\"\n-- some comment\n-- pgweb: user=\"foo\"\n\nselect 'foo'\n"
  },
  {
    "path": "data/lc_invalid_meta.sql",
    "chars": 51,
    "preview": "-- pgweb: host=\"localhost\" mode=\"foo\"\nselect 'foo'\n"
  },
  {
    "path": "data/lc_no_meta.sql",
    "chars": 13,
    "preview": "select 'foo'\n"
  },
  {
    "path": "data/passfile",
    "chars": 69,
    "preview": "localhost:5432:dbname:username:password\n127.0.0.1:5432:*:*:password2\n"
  },
  {
    "path": "data/roach.sql",
    "chars": 1381,
    "preview": "DROP DATABASE IF EXISTS \"roach\";\nCREATE DATABASE \"roach\";\nUSE \"roach\";\n\nCREATE TABLE product_information (\n  product_id "
  },
  {
    "path": "docker-compose-pg.yml",
    "chars": 1003,
    "preview": "---\nx-base: &base\n  environment: &env\n    POSTGRES_DB: pgweb\n    POSTGRES_PASSWORD: pgweb\n    POSTGRES_USER: pgweb\n  hea"
  },
  {
    "path": "docker-compose.yml",
    "chars": 856,
    "preview": "---\nservices:\n  postgres:\n    container_name: pgweb-postgres\n    image: postgres:15\n    ports:\n      - 5433:5432\n    vol"
  },
  {
    "path": "fly.toml",
    "chars": 660,
    "preview": "app = \"pgweb-demo\"\nkill_signal = \"SIGINT\"\nkill_timeout = 5\n\n[processes]\n  web = \"pgweb --sessions --bind=0.0.0.0 --metri"
  },
  {
    "path": "go.mod",
    "chars": 2845,
    "preview": "module github.com/sosedoff/pgweb\n\ngo 1.25\n\ntoolchain go1.25.4\n\nrequire (\n\tgithub.com/BurntSushi/toml v1.1.0\n\tgithub.com/"
  },
  {
    "path": "go.sum",
    "chars": 19992,
    "preview": "github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I=\ngithub.com/BurntSushi/toml v1.1.0/go.m"
  },
  {
    "path": "main.go",
    "chars": 90,
    "preview": "package main\n\nimport (\n\t\"github.com/sosedoff/pgweb/pkg/cli\"\n)\n\nfunc main() {\n\tcli.Run()\n}\n"
  },
  {
    "path": "pkg/api/api.go",
    "chars": 15637,
    "preview": "package api\n\nimport (\n\t\"context\"\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"net/http\"\n\tneturl \"net/url\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.co"
  },
  {
    "path": "pkg/api/api_test.go",
    "chars": 965,
    "preview": "package api\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc Test_assetContentType(t *testing.T) {\n\ts"
  },
  {
    "path": "pkg/api/errors.go",
    "chars": 546,
    "preview": "package api\n\nimport (\n\t\"errors\"\n)\n\nvar (\n\terrNotConnected         = errors.New(\"Not connected\")\n\terrNotPermitted        "
  },
  {
    "path": "pkg/api/helpers.go",
    "chars": 3998,
    "preview": "package api\n\nimport (\n\t\"fmt\"\n\t\"mime\"\n\t\"net/http\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/gin-goni"
  },
  {
    "path": "pkg/api/helpers_test.go",
    "chars": 2227,
    "preview": "package api\n\nimport (\n\t\"errors\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"testing\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"gi"
  },
  {
    "path": "pkg/api/logger.go",
    "chars": 2321,
    "preview": "package api\n\nimport (\n\t\"net/http\"\n\t\"regexp\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/sirupsen/logrus"
  },
  {
    "path": "pkg/api/logger_test.go",
    "chars": 713,
    "preview": "package api\n\nimport (\n\t\"net/http\"\n\t\"testing\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc "
  },
  {
    "path": "pkg/api/middleware.go",
    "chars": 1400,
    "preview": "package api\n\nimport (\n\t\"strings\"\n\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/sosedoff/pgweb/pkg/command\"\n)\n\n// Middleware"
  },
  {
    "path": "pkg/api/routes.go",
    "chars": 2115,
    "preview": "package api\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/sosedoff/pgweb/pkg/command\"\n\t\"github.com/sosedoff/pgweb/"
  },
  {
    "path": "pkg/api/session_manager.go",
    "chars": 2423,
    "preview": "package api\n\nimport (\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/sirupsen/logrus\"\n\n\t\"github.com/sosedoff/pgweb/pkg/client\"\n\t\"github.c"
  },
  {
    "path": "pkg/api/session_manager_test.go",
    "chars": 1947,
    "preview": "package api\n\nimport (\n\t\"sort\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/stretchr/testify/assert\"\n\n\t"
  },
  {
    "path": "pkg/api/types.go",
    "chars": 203,
    "preview": "package api\n\ntype localQuery struct {\n\tID          string `json:\"id\"`\n\tTitle       string `json:\"title,omitempty\"`\n\tDesc"
  },
  {
    "path": "pkg/bookmarks/bookmarks.go",
    "chars": 1533,
    "preview": "package bookmarks\n\nimport (\n\t\"os\"\n\n\t\"github.com/sosedoff/pgweb/pkg/command\"\n\t\"github.com/sosedoff/pgweb/pkg/shared\"\n)\n\n/"
  },
  {
    "path": "pkg/bookmarks/bookmarks_test.go",
    "chars": 2844,
    "preview": "package bookmarks\n\nimport (\n\t\"testing\"\n\n\t\"github.com/sosedoff/pgweb/pkg/command\"\n\t\"github.com/sosedoff/pgweb/pkg/shared\""
  },
  {
    "path": "pkg/bookmarks/manager.go",
    "chars": 2915,
    "preview": "package bookmarks\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/BurntSushi/toml\"\n)\n\ntype Ma"
  },
  {
    "path": "pkg/bookmarks/manager_test.go",
    "chars": 3156,
    "preview": "package bookmarks\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestManagerList(t *testing.T) {\n\te"
  },
  {
    "path": "pkg/cli/cli.go",
    "chars": 7770,
    "preview": "package cli\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"os/signal\"\n\t\"strings\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/gin-gon"
  },
  {
    "path": "pkg/client/client.go",
    "chars": 15260,
    "preview": "package client\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"log\"\n\tneturl \"net/url\"\n\t\"reflect\"\n\t\"regexp\"\n\t\"strings\"\n\t\"time\"\n\n\t"
  },
  {
    "path": "pkg/client/client_test.go",
    "chars": 21043,
    "preview": "package client\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"os/exec\"\n\t\"runtime\"\n\t\"sort\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/s"
  },
  {
    "path": "pkg/client/codec.go",
    "chars": 793,
    "preview": "package client\n\nimport (\n\t\"encoding/base64\"\n\t\"encoding/hex\"\n\t\"fmt\"\n\n\t\"github.com/mr-tron/base58\"\n)\n\nconst (\n\tCodecNone  "
  },
  {
    "path": "pkg/client/codec_test.go",
    "chars": 1195,
    "preview": "package client\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestSetBinaryCodec(t *testi"
  },
  {
    "path": "pkg/client/dump.go",
    "chars": 2115,
    "preview": "package client\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/url\"\n\t\"os/exec\"\n\t\"strings\"\n)\n\nvar (\n\tunsupportedDumpOpti"
  },
  {
    "path": "pkg/client/dump_test.go",
    "chars": 1556,
    "preview": "package client\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc testDumpExpor"
  },
  {
    "path": "pkg/client/result.go",
    "chars": 5152,
    "preview": "package client\n\nimport (\n\t\"bytes\"\n\t\"encoding/csv\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"log\"\n\t\"math\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.co"
  },
  {
    "path": "pkg/client/result_test.go",
    "chars": 3119,
    "preview": "package client\n\nimport (\n\t\"encoding/json\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/sosedoff/pgweb/pkg/command\"\n\t\"gith"
  },
  {
    "path": "pkg/client/tunnel.go",
    "chars": 4456,
    "preview": "package client\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net\"\n\t\"net/url\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"sync\"\n\t\"tim"
  },
  {
    "path": "pkg/client/util.go",
    "chars": 2749,
    "preview": "package client\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strings\"\n)\n\nvar (\n\t// List of keywords that are not allowed in read-only mod"
  },
  {
    "path": "pkg/client/util_test.go",
    "chars": 2358,
    "preview": "package client\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestDetectServerType(t *testing.T) {\n"
  },
  {
    "path": "pkg/command/options.go",
    "chars": 10123,
    "preview": "package command\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/user\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/jackc/pgpassfile\"\n"
  },
  {
    "path": "pkg/command/options_test.go",
    "chars": 3784,
    "preview": "package command\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/mitchellh/go-homedir\"\n\t\"github.com/stretchr/te"
  },
  {
    "path": "pkg/command/version.go",
    "chars": 1396,
    "preview": "package command\n\nimport (\n\t\"fmt\"\n\t\"runtime\"\n\t\"strings\"\n)\n\nconst (\n\t// Version is the current Pgweb application version\n\t"
  },
  {
    "path": "pkg/connect/backend.go",
    "chars": 2052,
    "preview": "package connect\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/sirupsen/logr"
  },
  {
    "path": "pkg/connect/backend_test.go",
    "chars": 4063,
    "preview": "package connect\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"net\"\n\t\"net/http\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"gith"
  },
  {
    "path": "pkg/connect/types.go",
    "chars": 526,
    "preview": "package connect\n\nimport \"errors\"\n\nvar (\n\terrBackendConnectError = errors.New(\"unable to connect to the auth backend\")\n\te"
  },
  {
    "path": "pkg/connection/connection_string.go",
    "chars": 4482,
    "preview": "package connection\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\tneturl \"net/url\"\n\t\"os\"\n\t\"os/user\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/jac"
  },
  {
    "path": "pkg/connection/connection_string_test.go",
    "chars": 8070,
    "preview": "package connection\n\nimport (\n\t\"fmt\"\n\t\"net/url\"\n\t\"os/user\"\n\t\"testing\"\n\n\t\"github.com/sosedoff/pgweb/pkg/command\"\n\t\"github."
  },
  {
    "path": "pkg/connection/port.go",
    "chars": 628,
    "preview": "package connection\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"strings\"\n)\n\n// IsPortAvailable returns true if there's no listene"
  },
  {
    "path": "pkg/connection/port_test.go",
    "chars": 1350,
    "preview": "package connection\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"os\"\n\t\"runtime\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc Te"
  },
  {
    "path": "pkg/history/history.go",
    "chars": 295,
    "preview": "package history\n\nimport (\n\t\"time\"\n)\n\ntype Record struct {\n\tQuery     string `json:\"query\"`\n\tTimestamp string `json:\"time"
  },
  {
    "path": "pkg/metrics/handler.go",
    "chars": 462,
    "preview": "package metrics\n\nimport (\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/prometheus/client_golang/prometheus/promhttp\"\n)\n\ntype Handle"
  },
  {
    "path": "pkg/metrics/metrics.go",
    "chars": 1148,
    "preview": "package metrics\n\nimport (\n\t\"time\"\n\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/prometheus/client_gola"
  },
  {
    "path": "pkg/metrics/server.go",
    "chars": 320,
    "preview": "package metrics\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/sirupsen/logrus\"\n)\n\nfunc StartServer(logger *logrus.Logger, path str"
  },
  {
    "path": "pkg/queries/field.go",
    "chars": 780,
    "preview": "package queries\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strings\"\n)\n\ntype field struct {\n\tvalue string\n\tre    *regexp.Regexp\n}\n\nfunc"
  },
  {
    "path": "pkg/queries/field_test.go",
    "chars": 1053,
    "preview": "package queries\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc Test_field(t *testing.T) {\n\tfield, e"
  },
  {
    "path": "pkg/queries/metadata.go",
    "chars": 3382,
    "preview": "package queries\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n)\n\nvar (\n\treMetaPrefix  = regexp.MustCompile(`("
  },
  {
    "path": "pkg/queries/metadata_test.go",
    "chars": 3163,
    "preview": "package queries\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc Test_parseFields(t *testing.T) {\n\tex"
  },
  {
    "path": "pkg/queries/query.go",
    "chars": 538,
    "preview": "package queries\n\ntype Query struct {\n\tID   string\n\tPath string\n\tMeta *Metadata\n\tData string\n}\n\n// IsPermitted returns tr"
  },
  {
    "path": "pkg/queries/query_test.go",
    "chars": 1769,
    "preview": "package queries\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestQueryIsPermitted(t *testing.T) {"
  },
  {
    "path": "pkg/queries/store.go",
    "chars": 1560,
    "preview": "package queries\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n)\n\nvar (\n\tErrQueryDirNotExist  = errors.New"
  },
  {
    "path": "pkg/queries/store_test.go",
    "chars": 1815,
    "preview": "//go:build !windows\n\npackage queries\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestStoreReadAl"
  },
  {
    "path": "pkg/shared/ssh_info.go",
    "chars": 484,
    "preview": "package shared\n\nimport (\n\t\"fmt\"\n)\n\n// SSHInfo contains ssh server configuration\ntype SSHInfo struct {\n\tHost        strin"
  },
  {
    "path": "pkg/statements/sql/databases.sql",
    "chars": 85,
    "preview": "SELECT\n  datname\nFROM\n  pg_database\nWHERE\n  NOT datistemplate\nORDER BY\n  datname ASC\n"
  },
  {
    "path": "pkg/statements/sql/estimated_row_count.sql",
    "chars": 105,
    "preview": "SELECT\n  reltuples\nFROM\n  pg_class\nWHERE\n  oid = ('\"' || $1::text || '\".\"' || $2::text || '\"')::regclass\n"
  },
  {
    "path": "pkg/statements/sql/function.sql",
    "chars": 152,
    "preview": "SELECT\n  p.oid,\n  p.proname,\n  p.pronamespace,\n  p.proowner,\n  pg_get_functiondef(oid) AS functiondef\nFROM\n  pg_catalog."
  },
  {
    "path": "pkg/statements/sql/info.sql",
    "chars": 187,
    "preview": "SELECT\n  session_user,\n  current_user,\n  current_database(),\n  current_schemas(false),\n  inet_client_addr(),\n  inet_clie"
  },
  {
    "path": "pkg/statements/sql/info_simple.sql",
    "chars": 99,
    "preview": "SELECT\n  session_user,\n  current_user,\n  current_database(),\n  current_schemas(false),\n  version()\n"
  },
  {
    "path": "pkg/statements/sql/materialized_view.sql",
    "chars": 327,
    "preview": "SELECT\n  attname AS column_name,\n  atttypid::regtype AS data_type,\n  (CASE WHEN attnotnull IS TRUE THEN 'NO' ELSE 'YES' "
  },
  {
    "path": "pkg/statements/sql/objects.sql",
    "chars": 1172,
    "preview": "WITH all_objects AS (\n  SELECT\n    c.oid,\n    n.nspname AS schema,\n    c.relname AS name,\n    CASE c.relkind\n      WHEN "
  },
  {
    "path": "pkg/statements/sql/schemas.sql",
    "chars": 187,
    "preview": "SELECT\n  schema_name\nFROM\n  information_schema.schemata\nWHERE\n  schema_name NOT IN ('information_schema', 'pg_catalog')\n"
  },
  {
    "path": "pkg/statements/sql/settings.sql",
    "chars": 26,
    "preview": "SELECT * FROM pg_settings\n"
  },
  {
    "path": "pkg/statements/sql/table_constraints.sql",
    "chars": 276,
    "preview": "SELECT\n  conname AS name,\n  pg_get_constraintdef(c.oid, true) AS definition\nFROM\n  pg_constraint c\nJOIN\n  pg_namespace n"
  },
  {
    "path": "pkg/statements/sql/table_indexes.sql",
    "chars": 235,
    "preview": "SELECT\n  indexname AS index_name,\n  pg_size_pretty(pg_table_size(('\"' || schemaname || '\".\"' || indexname || '\"')::regcl"
  },
  {
    "path": "pkg/statements/sql/table_info.sql",
    "chars": 244,
    "preview": "SELECT\n  pg_size_pretty(pg_table_size($1)) AS data_size,\n  pg_size_pretty(pg_indexes_size($1)) AS index_size,\n  pg_size_"
  },
  {
    "path": "pkg/statements/sql/table_info_cockroach.sql",
    "chars": 97,
    "preview": "SELECT\n  'n/a' AS data_size,\n  'n/a' AS index_size,\n  'n/a' AS total_size,\n  'n/a' AS rows_count\n"
  },
  {
    "path": "pkg/statements/sql/table_schema.sql",
    "chars": 323,
    "preview": "SELECT\n  column_name,\n  data_type,\n  is_nullable,\n  character_maximum_length,\n  character_set_catalog,\n  column_default,"
  },
  {
    "path": "pkg/statements/sql/tables_stats.sql",
    "chars": 1623,
    "preview": "WITH columns_counts AS (\n  SELECT table_schema, table_name, COUNT(1) AS num\n  FROM information_schema.columns\n  GROUP BY"
  },
  {
    "path": "pkg/statements/sql.go",
    "chars": 2124,
    "preview": "package statements\n\nimport (\n\t_ \"embed\"\n)\n\nvar (\n\t//go:embed sql/databases.sql\n\tDatabases string\n\n\t//go:embed sql/schema"
  },
  {
    "path": "pkg/util/profiler.go",
    "chars": 471,
    "preview": "package util\n\nimport (\n\t\"log\"\n\t\"os\"\n\t\"runtime\"\n\t\"time\"\n)\n\nconst MEGABYTE = 1024 * 1024\n\nfunc runProfiler() {\n\tlogger := "
  },
  {
    "path": "script/build_all.sh",
    "chars": 590,
    "preview": "#!/usr/bin/env bash\n\nTARGETS=\"darwin/amd64 darwin/arm64 linux/amd64 linux/arm64 windows/amd64\"\nARM_TARGETS=\"arm/v5 arm64"
  },
  {
    "path": "script/check_formatting.sh",
    "chars": 198,
    "preview": "#!/bin/bash\n\n# Get list of offending files\nfiles=\"$(go fmt ./pkg/...)\"\n\nif [ -n \"$files\" ]; then\n  echo \"Go code is not "
  },
  {
    "path": "script/package.sh",
    "chars": 189,
    "preview": "#!/bin/bash\n\nset -e\n\nDIR=\"./bin\"\nrm -f $DIR/*.zip\n\nfor file in $(ls $DIR)\ndo\n  fin=$DIR/$file\n  fout=$DIR/$file.zip\n  sh"
  },
  {
    "path": "script/test_all.sh",
    "chars": 971,
    "preview": "#!/bin/bash\n#\n# Integartion testing with dockerized Postgres servers\n#\n# Requires Docker for Mac to run on OSX.\n# Instal"
  },
  {
    "path": "script/test_cockroach.sh",
    "chars": 1201,
    "preview": "#!/bin/bash\n\nset -e\n\nfunction killproc() {\n  if [[ $(lsof -i tcp:8888) ]]; then\n    lsof -i tcp:8888 | grep pgweb | awk "
  },
  {
    "path": "script/update_homebrew.sh",
    "chars": 701,
    "preview": "#!/bin/bash\n\nRELEASE_FILE=\"./tmp/release.json\"\nHOMEBREW_ROOT=\"/usr/local/Homebrew/Library/Taps/homebrew/homebrew-core\"\ne"
  },
  {
    "path": "static/css/app.css",
    "chars": 14884,
    "preview": "#main {\n  display: none;\n}\n\n#nav {\n  position: fixed;\n  top: 0;\n  left: 250px;\n  right: 0;\n  height: 50px;\n  background:"
  },
  {
    "path": "static/css/bootstrap.css",
    "chars": 109518,
    "preview": "/*!\n * Bootstrap v3.2.0 (http://getbootstrap.com)\n * Copyright 2011-2014 Twitter, Inc.\n * Licensed under MIT (https://gi"
  },
  {
    "path": "static/css/font-awesome.css",
    "chars": 21984,
    "preview": "/*!\n *  Font Awesome 4.2.0 by @davegandy - http://fontawesome.io - @fontawesome\n *  License - http://fontawesome.io/lice"
  },
  {
    "path": "static/data.go",
    "chars": 356,
    "preview": "package static\n\nimport (\n\t\"embed\"\n\t\"net/http\"\n\t\"os\"\n)\n\n//go:embed img/* js/* css/* fonts/*\n//go:embed index.html\nvar ass"
  },
  {
    "path": "static/index.html",
    "chars": 15450,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\" xml:lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\">\n<head>\n  <title>pgweb</title>\n  <met"
  },
  {
    "path": "static/js/ace-pgsql.js",
    "chars": 59372,
    "preview": "ace.define(\"ace/mode/doc_comment_highlight_rules\",[\"require\",\"exports\",\"module\",\"ace/lib/oop\",\"ace/mode/text_highlight_r"
  },
  {
    "path": "static/js/ace.js",
    "chars": 375179,
    "preview": "(function(){function o(n){var i=e;n&&(e[n]||(e[n]={}),i=e[n]);if(!i.define||!i.define.packaged)t.original=i.define,i.def"
  },
  {
    "path": "static/js/app.js",
    "chars": 50923,
    "preview": "var appInfo             = {};\nvar appFeatures         = {};\nvar editor              = null;\nvar connected           = fa"
  },
  {
    "path": "static/js/base64.js",
    "chars": 3517,
    "preview": "/**\n*\n*  Base64 encode / decode\n*  http://www.webtoolkit.info/\n*\n**/\nvar Base64 = {\n  // private property\n  _keyStr : \"A"
  },
  {
    "path": "static/js/bootstrap-contextmenu.js",
    "chars": 5300,
    "preview": "/*!\n * Bootstrap Context Menu\n * Author: @sydcanem\n * https://github.com/sydcanem/bootstrap-contextmenu\n *\n * Inspired b"
  },
  {
    "path": "static/js/bootstrap-dropdown.js",
    "chars": 4384,
    "preview": "/* ========================================================================\n * Bootstrap: dropdown.js v3.2.0\n * http://g"
  },
  {
    "path": "static/js/ext-language_tools.js",
    "chars": 39071,
    "preview": "ace.define(\"ace/snippets\",[\"require\",\"exports\",\"module\",\"ace/lib/oop\",\"ace/lib/event_emitter\",\"ace/lib/lang\",\"ace/range\""
  },
  {
    "path": "static/js/jquery.js",
    "chars": 84245,
    "preview": "/*! jQuery v2.1.1 | (c) 2005, 2014 jQuery Foundation, Inc. | jquery.org/license */\n!function(a,b){\"object\"==typeof modul"
  },
  {
    "path": "static/js/theme-tomorrow.js",
    "chars": 2556,
    "preview": "ace.define(\"ace/theme/tomorrow\",[\"require\",\"exports\",\"module\",\"ace/lib/dom\"],function(e,t,n){t.isDark=!1,t.cssClass=\"ace"
  },
  {
    "path": "static/js/utils.js",
    "chars": 831,
    "preview": "if (!Array.prototype.forEach) {\n  // Simplified iterator for browsers without forEach support\n  Array.prototype.forEach "
  }
]

// ... and 1 more files (download for full content)

About this extraction

This page contains the full source code of the sosedoff/pgweb GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 135 files (1.0 MB), approximately 331.7k tokens, and a symbol index with 681 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!