master 33162203dba8 cached
31 files
46.2 KB
13.3k tokens
1 requests
Download .txt
Repository: caiiiycuk/postgresql-to-sqlite
Branch: master
Commit: 33162203dba8
Files: 31
Total size: 46.2 KB

Directory structure:
gitextract_jwx89zhw/

├── .github/
│   └── workflows/
│       └── build.yml
├── .gitignore
├── .travis.yml
├── Dockerfile
├── LICENSE
├── README.md
├── project/
│   ├── Build.scala
│   ├── build.properties
│   └── plugins.sbt
├── scalastyle-config.xml
└── src/
    ├── main/
    │   └── scala/
    │       └── com/
    │           └── github/
    │               └── caiiiycuk/
    │                   └── pg2sqlite/
    │                       ├── Boot.scala
    │                       ├── Config.scala
    │                       ├── Connection.scala
    │                       ├── DumpInserter.scala
    │                       ├── Log.scala
    │                       ├── LoggedIterator.scala
    │                       ├── command/
    │                       │   ├── Command.scala
    │                       │   ├── CommandException.scala
    │                       │   ├── Copy.scala
    │                       │   ├── CreateIndex.scala
    │                       │   └── CreateTable.scala
    │                       ├── dsl/
    │                       │   └── DSL.scala
    │                       ├── iterator/
    │                       │   ├── Line.scala
    │                       │   └── LineIterator.scala
    │                       ├── schema/
    │                       │   ├── Column.scala
    │                       │   └── Schema.scala
    │                       └── values/
    │                           ├── LineToValues.scala
    │                           ├── Value.scala
    │                           └── ValueParseException.scala
    └── test/
        └── scala/
            └── com/
                └── github/
                    └── caiiiycuk/
                        └── pg2sqlite/
                            └── dsl/
                                ├── DSLTest.scala
                                └── DumperTest.scala

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

================================================
FILE: .github/workflows/build.yml
================================================
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.

name: Build

on:
  push:
    branches: [ "master" ]
  pull_request:
    branches: [ "master" ]

permissions:
  contents: read

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    - name: Set up JDK 8
      uses: actions/setup-java@v3
      with:
        java-version: '8'
        distribution: 'temurin'
        cache: 'sbt'
    - name: Run tests
      run: sbt test
    - name: Build one-jar
      run: sbt one-jar
    - name: Upload a Build Artifact
      uses: actions/upload-artifact@v3.1.2
      with:
        name: postgresql-to-sqlite
        path: target/scala-2.11/postgresql-to-sqlite*.jar


================================================
FILE: .gitignore
================================================
*.class
*.log

# sbt specific
.cache
.history
.lib/
dist/*
target/
lib_managed/
src_managed/
project/boot/
project/plugins/project/

# Scala-IDE specific
.scala_dependencies
.worksheet
/bin/


================================================
FILE: .travis.yml
================================================
language: scala
scala:
   - 2.11.12
branches:
  only:
    - master
jdk:
  - openjdk9

script:
  - sbt ++$TRAVIS_SCALA_VERSION test
  - sbt ++$TRAVIS_SCALA_VERSION one-jar


================================================
FILE: Dockerfile
================================================
FROM hseeberger/scala-sbt:8u222_1.3.5_2.13.1
ENV psource=database.dump
ENV starget=sqllight.db
RUN mkdir -p /p2s
WORKDIR /p2s
COPY . ./
RUN sbt one-jar
RUN cp target/scala-2.11/postgresql-to-sqlite_2.11-*-one-jar.jar pg2sqlite.jar
CMD exec java -jar pg2sqlite.jar -d "$psource" -o "$starget"


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

Copyright (c) 2015 Aleksander Guryanov

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: README.md
================================================
# postgresql-to-sqlite (pg2sqlite)
[![Build](https://github.com/caiiiycuk/postgresql-to-sqlite/actions/workflows/build.yml/badge.svg)](https://github.com/caiiiycuk/postgresql-to-sqlite/actions/workflows/build.yml)

Easy to use solution to create sqlite database from postgresql dump.

* default [`pg_dump`](http://www.postgresql.org/docs/9.4/static/app-pgdump.html) script format
* as fast as possible
* silently ignore unsupported postgresql features
* gzip support

## Installing

In [release section](https://github.com/caiiiycuk/postgresql-to-sqlite/releases/) you can download pre-built version of pg2sqlite.jar

## How to use

1. Install jre (java) on your PC

2. Create dump from postgresql database
```sh
pg_dump -h host -U user -f database.dump database
```

3. Make sqlite database from it
```
java -jar pg2sqlite-1.0.3.jar -d database.dump -o sqlite.db
```

## Command line arguments

`pg2sqlite -d <file> -o <file> [-f <true|false>]`

* **-d** `<file>` - file that contains dump of postgresql database (made by pg_dump, accepts .gz)
* **-o** `<file>` - file name of newly created sqlite3 database
* **-f** `<true|false>` - default: false, force database re-creation if database file alredy exists
* **-t** `<integer|text|real>` - default: integer, change sqlite3 date class (read below)

## Timestamps

SQLite does not have a storage class set aside for storing dates and/or times. Instead, the built-in [Date And Time Functions](https://www.sqlite.org/lang_datefunc.html) of SQLite are capable of storing dates and times as TEXT, REAL, or INTEGER values:

* TEXT as ISO8601 strings ("YYYY-MM-DD HH:MM:SS.SSS").
* REAL as Julian day numbers, the number of days since noon in Greenwich on November 24, 4714 B.C. according to the proleptic Gregorian calendar.
* INTEGER as Unix Time, the number of seconds since 1970-01-01 00:00:00 UTC.

By default pg2sqlite uses **INTEGER** to store dates, but you can change this with **-t** argument (`-t text` or `-t real`), use it like this:

```sh
java -jar pg2sqlite-1.0.3.jar -d database.dump -o sqlite.db -t text
```

## Tips

pg2sqlite does not support database schemas. If your dump file includes schema definition It will print errors like this:
```
Create Table - Exception:
unknown database <schema>
[SQL] 'CREATE TABLE <schema>.table (...;'
```
You can easily fix dump file with `sed`:
```sh
# sed 's/<schema name>\.//' -i  database.dump
sed 's/public\.//' -i  database.dump
pg2sqlite -d output.dump -o sqlite.db
```
Where `public` is a schema name.

## How to build
```sh
git clone https://github.com/caiiiycuk/postgresql-to-sqlite.git
cd postgresql-to-sqlite
sbt one-jar
cp target/scala-2.11/postgresql-to-sqlite_2.11-0.0.1-SNAPSHOT-one-jar.jar pg2sqlite.jar
```

## Docker

Clone the repository and run 
```
docker build -t postgresql-to-sqlite:latest .
```
inside the postgresql-to-sqlite folder.

Use 
```
docker run -v /home/john/dbdata:/dbdata -e psource='/dbdata/pqdump.sql' -e starget='/dbdata/output.sqlite'  -it postgresql-to-sqlite:latest
```
where
- -v: is the volume where the pqdump file is located. (and later the output file)
- -e: `psource` is the pqdump filename and folder & `starget` the sqlite filename and folder

p.s. the schema removal has to be done outside the container

## Support

If you appreciate this project, please consider voting for it on Stack Overflow:

https://stackoverflow.com/questions/6148421/how-to-convert-a-postgres-database-to-sqlite/69293251#69293251


================================================
FILE: project/Build.scala
================================================
import com.github.retronym.SbtOneJar
import sbt._
import Keys._

object Build extends Build {

  lazy val project = Project("root", file("."), settings = Seq(
    name := "postgresql-to-sqlite",
    organization := "com.github.caiiiycuk",
    version := "1.1.1",
    scalaVersion := "2.11.12",

    libraryDependencies ++= Seq(
      "com.github.scopt" %% "scopt" % "3.3.0",
      "ch.qos.logback" % "logback-classic" % "1.1.2",
      "org.xerial" % "sqlite-jdbc" % "3.42.0.0",
      "org.scalatest" %% "scalatest" % "2.2.4" % "test"
    )
  ) ++ SbtOneJar.oneJarSettings)

}


================================================
FILE: project/build.properties
================================================
sbt.version=0.13.18


================================================
FILE: project/plugins.sbt
================================================
addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "4.0.0")

addSbtPlugin("org.scalastyle" %% "scalastyle-sbt-plugin" % "0.7.0")

addSbtPlugin("org.scala-sbt.plugins" % "sbt-onejar" % "0.8")


================================================
FILE: scalastyle-config.xml
================================================
<scalastyle>
 <name>Scalastyle standard configuration</name>
 <check level="error" class="org.scalastyle.file.FileTabChecker" enabled="true"></check>
 <check level="error" class="org.scalastyle.file.FileLengthChecker" enabled="true">
  <parameters>
   <parameter name="maxFileLength"><![CDATA[800]]></parameter>
  </parameters>
 </check>
 <check level="error" class="org.scalastyle.scalariform.SpacesAfterPlusChecker" enabled="true"></check>
 <check level="error" class="org.scalastyle.file.WhitespaceEndOfLineChecker" enabled="true"></check>
 <check level="error" class="org.scalastyle.scalariform.SpacesBeforePlusChecker" enabled="true"></check>
 <check level="error" class="org.scalastyle.file.FileLineLengthChecker" enabled="true">
  <parameters>
   <parameter name="maxLineLength"><![CDATA[160]]></parameter>
   <parameter name="tabSize"><![CDATA[4]]></parameter>
  </parameters>
 </check>
 <check level="error" class="org.scalastyle.scalariform.ClassNamesChecker" enabled="true">
  <parameters>
   <parameter name="regex"><![CDATA[[A-Z][A-Za-z]*]]></parameter>
  </parameters>
 </check>
 <check level="error" class="org.scalastyle.scalariform.ObjectNamesChecker" enabled="true">
  <parameters>
   <parameter name="regex"><![CDATA[[A-Z][A-Za-z]*]]></parameter>
  </parameters>
 </check>
 <check level="error" class="org.scalastyle.scalariform.PackageObjectNamesChecker" enabled="true">
  <parameters>
   <parameter name="regex"><![CDATA[^[a-z][A-Za-z]*$]]></parameter>
  </parameters>
 </check>
 <check level="error" class="org.scalastyle.scalariform.EqualsHashCodeChecker" enabled="true"></check>
 <check level="error" class="org.scalastyle.scalariform.IllegalImportsChecker" enabled="true">
  <parameters>
   <parameter name="illegalImports"><![CDATA[sun._,java.awt._]]></parameter>
  </parameters>
 </check>
 <check level="error" class="org.scalastyle.scalariform.ParameterNumberChecker" enabled="true">
  <parameters>
   <parameter name="maxParameters"><![CDATA[8]]></parameter>
  </parameters>
 </check>
 <check level="error" class="org.scalastyle.scalariform.MagicNumberChecker" enabled="true">
  <parameters>
   <parameter name="ignore"><![CDATA[-1,0,1,2,3]]></parameter>
  </parameters>
 </check>
 <check level="error" class="org.scalastyle.scalariform.NoWhitespaceBeforeLeftBracketChecker" enabled="true"></check>
 <check level="error" class="org.scalastyle.scalariform.NoWhitespaceAfterLeftBracketChecker" enabled="true"></check>
 <check level="error" class="org.scalastyle.scalariform.ReturnChecker" enabled="true"></check>
 <check level="error" class="org.scalastyle.scalariform.NullChecker" enabled="true"></check>
 <check level="error" class="org.scalastyle.scalariform.NoCloneChecker" enabled="true"></check>
 <check level="error" class="org.scalastyle.scalariform.NoFinalizeChecker" enabled="true"></check>
 <check level="error" class="org.scalastyle.scalariform.CovariantEqualsChecker" enabled="true"></check>
 <check level="error" class="org.scalastyle.scalariform.StructuralTypeChecker" enabled="true"></check>
 <check level="error" class="org.scalastyle.file.RegexChecker" enabled="true">
  <parameters>
   <parameter name="regex"><![CDATA[println]]></parameter>
  </parameters>
 </check>
 <check level="error" class="org.scalastyle.scalariform.NumberOfTypesChecker" enabled="true">
  <parameters>
   <parameter name="maxTypes"><![CDATA[30]]></parameter>
  </parameters>
 </check>
 <check level="error" class="org.scalastyle.scalariform.CyclomaticComplexityChecker" enabled="false">
  <parameters>
   <parameter name="maximum"><![CDATA[10]]></parameter>
  </parameters>
 </check>
 <check level="error" class="org.scalastyle.scalariform.UppercaseLChecker" enabled="true"></check>
 <check level="error" class="org.scalastyle.scalariform.SimplifyBooleanExpressionChecker" enabled="true"></check>
 <check level="error" class="org.scalastyle.scalariform.IfBraceChecker" enabled="true">
  <parameters>
   <parameter name="singleLineAllowed"><![CDATA[true]]></parameter>
   <parameter name="doubleLineAllowed"><![CDATA[false]]></parameter>
  </parameters>
 </check>
 <check level="error" class="org.scalastyle.scalariform.MethodLengthChecker" enabled="true">
  <parameters>
   <parameter name="maxLength"><![CDATA[50]]></parameter>
  </parameters>
 </check>
 <check level="error" class="org.scalastyle.scalariform.MethodNamesChecker" enabled="true">
  <parameters>
   <parameter name="regex"><![CDATA[^[a-z][A-Za-z0-9]*$]]></parameter>
  </parameters>
 </check>
 <check level="error" class="org.scalastyle.scalariform.NumberOfMethodsInTypeChecker" enabled="true">
  <parameters>
   <parameter name="maxMethods"><![CDATA[30]]></parameter>
  </parameters>
 </check>
 <check level="error" class="org.scalastyle.scalariform.PublicMethodsHaveTypeChecker" enabled="false"></check>
 <check level="error" class="org.scalastyle.file.NewLineAtEofChecker" enabled="true"></check>
 <check level="error" class="org.scalastyle.file.NoNewLineAtEofChecker" enabled="false"></check>
</scalastyle>


================================================
FILE: src/main/scala/com/github/caiiiycuk/pg2sqlite/Boot.scala
================================================
package com.github.caiiiycuk.pg2sqlite

import com.github.caiiiycuk.pg2sqlite.command.CommandException
import com.github.caiiiycuk.pg2sqlite.iterator.LineIterator
import com.github.caiiiycuk.pg2sqlite.values.ValueParseException

import ch.qos.logback.classic.Level

object Boot extends App with Log {

  val root = org.slf4j.LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME).asInstanceOf[ch.qos.logback.classic.Logger]
  root.setLevel(Level.INFO)

  val config = Config.parse(args)
  import config._

  val size = pgdump.length()
  val connection = Connection.sqlite(sqlite, config.dateClass)
  val iterator = LineIterator(pgdump)
  val loggedIterator = LoggedIterator(iterator, () => 100.0 * iterator.readed / size)
  val dumpInserter = new DumpInserter(connection)

  log.info(s"'$pgdump' (${toMb(size)} Mb) -> '$sqlite'")

  val success = try {
    dumpInserter.insert(loggedIterator)
    true
  } catch {
    case e: CommandException =>
      log.error(e.getMessage)
      false
    case e: ValueParseException =>
      log.error(e.getMessage)
      false
    case e: Throwable =>
      log.error(e.getMessage, e)
      false
  }

  iterator.close
  connection.close

  if (success) {
    log.info("Well done...")
  } else {
    log.error("Task failed...")
  }
}


================================================
FILE: src/main/scala/com/github/caiiiycuk/pg2sqlite/Config.scala
================================================
package com.github.caiiiycuk.pg2sqlite

import java.io.File

case class Config(pgdump: File = new File("dump"), sqlite: File = new File("db"),
                  force: Boolean = false, dateClass: String = Connection.DEFAULT_DATE_CLASS)

object Config extends Log {
  private val parser = new scopt.OptionParser[Config]("postgresql-to-sqlite") {
    head("postgresql-to-sqlite")

    opt[File]('d', "dump") required() valueName ("<dump file>") action { (v, c) =>
      c.copy(pgdump = v)
    } text ("postgresql dump generated by pg_dump")

    opt[File]('o', "out") required() valueName ("<sqlite3 database>") action { (v, c) =>
      c.copy(sqlite = v)
    } text ("sqlite3 database to create")

    opt[Boolean]('f', "force") optional() valueName ("<true|false>") action { (v, c) =>
      c.copy(force = v)
    } text ("recreate database if exists")

    opt[String]('t', "timestamps") optional() valueName ("<integer|text|real>") action { (v, c) =>
      val dc = v.toUpperCase()
      if (dc.equals(Connection.TEXT_DATE_CLASS) || dc.equals(Connection.REAL_DATE_CLASS)) {
        c.copy(dateClass = dc)
      } else {
        c
      }
    } text ("Change sqlite3 date class (default: INTEGER)")

    checkConfig { c =>
      import c._

      if (!pgdump.exists()) {
        failure(s"Dump '${pgdump}' does not exists")
      } else if (sqlite.exists()) {
        if (force) {
          sqlite.delete()
          success
        } else {
          failure(s"Database '${sqlite}' already exists")
        }
      } else {
        success
      }
    }
  }

  def parse(args: Array[String]) = {
    parser.parse(args, Config()) match {
      case Some(config) =>
        Option(System.getenv("SQLITE_TMPDIR")) match {
          case None =>
            log.warn("You should set SQLITE_TMPDIR environment variable to control where sqlite stores temp files")
          case _ =>
        }

        config
      case _ =>
        System.exit(1)
        ???
    }
  }

}


================================================
FILE: src/main/scala/com/github/caiiiycuk/pg2sqlite/Connection.scala
================================================
package com.github.caiiiycuk.pg2sqlite

import org.sqlite.SQLiteConfig

import java.sql.DriverManager
import java.sql.Statement
import java.sql.PreparedStatement
import scala.collection.mutable.ListBuffer
import java.sql.ResultSet
import scala.annotation.tailrec
import java.io.File
import java.util.Properties

trait ConnectionHolder {
  def makeConnection: java.sql.Connection

  def db: String
}

object Connection {
  final val DEFAULT_DATE_CLASS = "INTEGER"
  final val TEXT_DATE_CLASS = "TEXT"
  final val REAL_DATE_CLASS = "REAL"
  private final val DATE_CLASS_PRAGMA = "date_class"
  private final val FETCH_SIZE = 8192
  private final val MAX_VARIABLE_NUMBER = 999

  def sqlite(dbFile: File, dateClass: String = DEFAULT_DATE_CLASS): Connection = {
    val connectionHolder = new ConnectionHolder {
      override def makeConnection: java.sql.Connection = {
        val properties = new Properties()
        properties.setProperty(DATE_CLASS_PRAGMA, dateClass)
        implicit val connection = DriverManager.getConnection(s"jdbc:sqlite:$dbFile", properties)

        connection.setAutoCommit(true)
        sqlitePragmas()

        connection.setAutoCommit(false)
        connection
      }

      override def db = dbFile.toString
    }

    new Connection(connectionHolder)
  }

  private def sqlitePragmas()(implicit connection: java.sql.Connection) = {
    assert(SQLiteConfig.Pragma.DATE_CLASS.pragmaName.equals(DATE_CLASS_PRAGMA));
    val statement = connection.createStatement()
    statement.executeUpdate(s"PRAGMA ${SQLiteConfig.Pragma.SYNCHRONOUS.pragmaName} = OFF")
    statement.executeUpdate(s"PRAGMA ${SQLiteConfig.Pragma.JOURNAL_MODE.pragmaName} = OFF")
    statement.executeUpdate(s"PRAGMA ${SQLiteConfig.Pragma.LIMIT_WORKER_THREADS.pragmaName} = 64")
    statement.executeUpdate(s"PRAGMA ${SQLiteConfig.Pragma.MAX_PAGE_COUNT.pragmaName} = 2147483646")
    statement.executeUpdate(s"PRAGMA ${SQLiteConfig.Pragma.CACHE_SIZE.pragmaName} = 65536")
    statement.executeUpdate("PRAGMA cache_spill = true")
    statement.close
  }
}

class Connection(connectionHolder: ConnectionHolder) {

  import Connection._

  final val MAX_VARIABLE_NUMBER = Connection.MAX_VARIABLE_NUMBER

  lazy val connection = connectionHolder.makeConnection

  lazy val db = connectionHolder.db

  def withStatement[T](block: (Statement) => T): T = {
    val statement = connection.createStatement()
    val t = block(statement)
    statement.close
    t
  }

  def withPreparedStatement[T](sql: String, keepAlive: Boolean = false)(block: (PreparedStatement) => T): T = {
    val statement = connection.prepareStatement(sql)
    statement.setFetchSize(FETCH_SIZE)

    val t = block(statement)
    if (!keepAlive) statement.close
    t
  }

  def close = {
    connection.commit
    connection.close
  }

  def execute(sql: String) = {
    withStatement { statement =>
      statement.executeUpdate(sql)
    }
  }

}


================================================
FILE: src/main/scala/com/github/caiiiycuk/pg2sqlite/DumpInserter.scala
================================================
package com.github.caiiiycuk.pg2sqlite

import scala.annotation.tailrec
import com.github.caiiiycuk.pg2sqlite.command._
import com.github.caiiiycuk.pg2sqlite.iterator.Line
import com.github.caiiiycuk.pg2sqlite.schema.Schema

object DumpInserter {
  val COMMANDS = List(CreateTable, Copy, CreateIndex)
}

class DumpInserter(connection: Connection) {

  import DumpInserter._

  implicit val schema = new Schema()

  @tailrec
  final def insert(iterator: Iterator[Line]): Unit = {
    if (iterator.hasNext) {
      val head = iterator.next()
      val fullIterator = Iterator(head) ++ iterator

      COMMANDS.find(_.matchHead(head)).foreach { command =>
        command.apply(connection, fullIterator)
      }

      insert(iterator)
    }
  }

}


================================================
FILE: src/main/scala/com/github/caiiiycuk/pg2sqlite/Log.scala
================================================
package com.github.caiiiycuk.pg2sqlite

import org.slf4j.LoggerFactory

trait Log {

  protected lazy val log = LoggerFactory.getLogger(getClass)

  def toMb(length: Long) = {
    length / 1024 / 1024
  }

  def humanizeMsTime(time: Long) = {
    val ms = time % 1000
    val s = time / 1000 % 60
    val m = time / 1000 / 60

    s"${m}m ${s}s ${ms}ms"
  }

  def humanizeElapsedAndRemaning(startAt: Long, progress: Double): String = {
    val elapsed = System.currentTimeMillis - startAt
    val remaining = (elapsed / progress - elapsed).toInt

    s"elapsed: ${humanizeMsTime(elapsed)} / remaining: ${humanizeMsTime(remaining)}"
  }

}


================================================
FILE: src/main/scala/com/github/caiiiycuk/pg2sqlite/LoggedIterator.scala
================================================
package com.github.caiiiycuk.pg2sqlite

object LoggedIterator {
  final val DEFAULT_SENSIVITY = 10
}

case class LoggedIterator[T](iterator: Iterator[T],
                             progress: () => Double, sensivity: Int = LoggedIterator.DEFAULT_SENSIVITY)
    extends Iterator[T] with Log {

  val startAt = System.currentTimeMillis
  var currentProgress: Long = 0L

  override def hasNext = iterator.hasNext

  override def next(): T = {
    val value = iterator.next
    val newProgress = progress()
    val intProgress = (newProgress * sensivity).toLong

    if (intProgress > currentProgress) {
      val elapsedAndRemaining = humanizeElapsedAndRemaning(startAt, newProgress / 100)
      log.info(s"Progress ${intProgress.toDouble / sensivity}%, ${elapsedAndRemaining}...\t")
      currentProgress = intProgress
    }

    value
  }

}


================================================
FILE: src/main/scala/com/github/caiiiycuk/pg2sqlite/command/Command.scala
================================================
package com.github.caiiiycuk.pg2sqlite.command

import scala.util.matching.Regex
import scala.annotation.tailrec
import com.github.caiiiycuk.pg2sqlite.Connection
import com.github.caiiiycuk.pg2sqlite.iterator.Line
import com.github.caiiiycuk.pg2sqlite.schema.Schema

trait Command {

  def matchHead(head: Line): Boolean =
    matchHead(head.text)

  def matchHead(head: String): Boolean

  def apply(connection: Connection, iterator: Iterator[Line])(implicit schema: Schema)

  @tailrec
  final protected def takeUntil(iterator: Iterator[Line],
                                when: (String) => Boolean,
                                buffer: List[Line] = Nil): List[Line] = {
    if (!iterator.hasNext) {
      buffer.reverse
    } else {
      val line = iterator.next
      val newBuffer = line :: buffer

      if (when(line.text)) {
        newBuffer.reverse
      } else {
        takeUntil(iterator, when, newBuffer)
      }
    }
  }

}


================================================
FILE: src/main/scala/com/github/caiiiycuk/pg2sqlite/command/CommandException.scala
================================================
package com.github.caiiiycuk.pg2sqlite.command

import com.github.caiiiycuk.pg2sqlite.iterator.Line

case class CommandException(command: String, cause: Throwable, context: List[String])
  extends Exception(s"""
$command - Exception:
\t${cause.getMessage}
\t${context.mkString("\n\t")},
""", cause)

object CommandException {
  def apply(command: String, cause: Throwable, sql: String, rows: List[Line], context: List[String] = Nil): CommandException = {
    val default = List(s"[SQL] '$sql'", s"[LINE #${rows.head.num}] ${rows.mkString(" ")}")
    CommandException(command, cause, default ++ context)
  }
}


================================================
FILE: src/main/scala/com/github/caiiiycuk/pg2sqlite/command/Copy.scala
================================================
package com.github.caiiiycuk.pg2sqlite.command

import com.github.caiiiycuk.pg2sqlite.Connection
import com.github.caiiiycuk.pg2sqlite.Log
import com.github.caiiiycuk.pg2sqlite.values.LineToValues
import java.sql.SQLException
import com.github.caiiiycuk.pg2sqlite.iterator.Line
import com.github.caiiiycuk.pg2sqlite.schema.Schema
import com.github.caiiiycuk.pg2sqlite.values.ValueParseException
import com.github.caiiiycuk.pg2sqlite.dsl.DSL._

object Copy extends Command with Log {

  import LineToValues._

  private val TABLE_NAME_POSITION = 1
  private val activator = "^(?i)copy".r

  override def matchHead(head: String): Boolean = {
    activator.findFirstIn(head).isDefined
  }

  override def apply(connection: Connection, iterator: Iterator[Line])(implicit schema: Schema) = {
    val rows = takeUntil(iterator, _.contains(";"))
    val rawSql = rows.mkString(" ")

    val (tableName, sql, columnTypes) = try {
      val tableName = rawSql.tokens(TABLE_NAME_POSITION)
      val columns = rawSql.takeBraces.head.columns.map(_.name).toList

      val marks = ("?," * columns.size).dropRight(1)
      val sql = s"insert into $tableName(${columns.map(column => s"[$column]").mkString(",")}) values($marks)"

      val columnTypes = schema.columnsToTypeConstants(tableName, columns)

      (tableName, sql, columnTypes)
    } catch {
      case t: Throwable =>
        throw CommandException(s"COPY - Unable to find TABLE NAME or COLUMNS in '$rawSql'",
          t, rawSql, rows)
    }

    if (schema.shouldExcludeTable(tableName)) {
      log.info(s"Skipping '$sql'")
    } else {
      log.info(s"COPY table '$tableName'")
      connection.withPreparedStatement(sql) { statement =>
        iterator.takeWhile(!_.startsWith("\\.")).foreach { row =>
          val values = try {
            toValues(row.text)(columnTypes)
          } catch {
            case e: ValueParseException =>
              throw CommandException("COPY", e, sql, rows,
                List(s"[DATA #${row.num}] '$row'",
                  s"[COLUMN,TYPE] ${schema.columns(tableName).map(_.toString).mkString(" ")}"))
          }

          try {
            values.foreach(_.apply(statement))
            statement.executeUpdate()
          } catch {
            case e: SQLException =>
              val vals = values.map(_.toString).mkString(", ")
              throw CommandException("COPY", e, sql, rows,
                List(s"[DATA #${row.num}] '$row'", s"[VALUES] '$vals'"))
          }

        }
      }
    }
  }

}


================================================
FILE: src/main/scala/com/github/caiiiycuk/pg2sqlite/command/CreateIndex.scala
================================================
package com.github.caiiiycuk.pg2sqlite.command

import java.sql.SQLException
import scala.annotation.tailrec
import com.github.caiiiycuk.pg2sqlite.Connection
import com.github.caiiiycuk.pg2sqlite.iterator.Line
import com.github.caiiiycuk.pg2sqlite.schema.Schema
import com.github.caiiiycuk.pg2sqlite.dsl.DSL._
import com.github.caiiiycuk.pg2sqlite.Log

object CreateIndex extends Command with Log {

  private val INDEX_NAME_POSITION = 2
  private val TABLE_NAME_POSITION = 0
  private val activator = """^(?i)create\s+index""".r

  override def matchHead(head: String): Boolean = {
    activator.findFirstIn(head).isDefined
  }

  override def apply(connection: Connection, iterator: Iterator[Line])(implicit schema: Schema) = {
    val rows = takeUntil(iterator, _.contains(";"))
    val rawSql = rows.mkString(" ").toLowerCase

    val (tableName, sql, columns) = try {
      val createIndexParts = rawSql.split("""\s+on\s+""")
      val indexName = createIndexParts(0).tokens(INDEX_NAME_POSITION)
      val tableName = createIndexParts(1).tokens(TABLE_NAME_POSITION)
      val columns = rawSql.takeBraces.head.columns.map(column => s"[${column.name}]").mkString(",")

      (tableName, s"CREATE INDEX $indexName ON $tableName ($columns)", columns)
    } catch {
      case t: Throwable =>
        throw CommandException(s"CREATE INDEX - Unable to find INDEX_NAME or TABLE NAME or COLUMNS in '$rawSql'",
          t, rawSql, rows)
    }

    if (schema.shouldExcludeTable(tableName) ||
      columns.isEmpty) {
      log.info(s"Skipping '$sql'")
    } else {
      try {
        connection.execute(sql)
      } catch {
        case e: SQLException =>
          throw CommandException("Create Index", e, sql, rows)
      }
    }
  }

}


================================================
FILE: src/main/scala/com/github/caiiiycuk/pg2sqlite/command/CreateTable.scala
================================================
package com.github.caiiiycuk.pg2sqlite.command

import java.sql.SQLException
import scala.annotation.tailrec
import com.github.caiiiycuk.pg2sqlite.Connection
import com.github.caiiiycuk.pg2sqlite.iterator.Line
import com.github.caiiiycuk.pg2sqlite.schema.Schema
import com.github.caiiiycuk.pg2sqlite.Log
import com.github.caiiiycuk.pg2sqlite.dsl.DSL._

object CreateTable extends Command with Log {

  private final val TABLE_NAME_POSITON = 2
  private final val activator = """^(?i)create\s+table""".r

  override def matchHead(head: String): Boolean = {
    activator.findFirstIn(head).isDefined
  }

  override def apply(connection: Connection, iterator: Iterator[Line])(implicit schema: Schema) = {
    val rows = takeUntil(iterator, _.contains(";"))
    val rawSql = rows.mkString(" ")

    val (tableName, sql) = try {
      val table = rawSql.tokens(TABLE_NAME_POSITON)
      val columns = rawSql.takeBraces.head.columns

      columns.foreach { column =>
        schema.addColumn(table, column)
      }

      (table, s"CREATE TABLE [$table] (${columns.map(column => s"[${column.name}]").mkString(", ")});")
    } catch {
      case t: Throwable =>
        throw CommandException(s"CREATE TABLE - Unable to find TABLE NAME or COLUMNS in '$rawSql'",
          t, rawSql, rows)
    }

    if (schema.shouldExcludeTable(tableName)) {
      log.info(s"Skipping '$sql'")
    } else {
      try {
        connection.execute(sql)
      } catch {
        case e: SQLException =>
          throw CommandException("Create Table", e, sql, rows)
      }
    }
  }

}


================================================
FILE: src/main/scala/com/github/caiiiycuk/pg2sqlite/dsl/DSL.scala
================================================
package com.github.caiiiycuk.pg2sqlite.dsl

import scala.annotation.tailrec
import com.github.caiiiycuk.pg2sqlite.schema.Column

class DSL(line: String) {

  import DSL._

  def dropBraces: String =
    dropBraces(line.toIterator)

  def takeBraces: List[String] = {
    takeBraces(line.toIterator)
  }

  def commaSplitRespectBraces: List[String] = {
    commaSplitRespectBraces(line.toIterator)
  }

  def tokens: List[String] = {
    line.replaceAll("\"|'","").split("""\s|:|,|\(|\)""").map(_.trim).filterNot(_.isEmpty).toList
  }

  def columns: List[Column] = {
    val columns = commaSplitRespectBraces(line.toIterator).map(_.trim).filterNot(_.isEmpty)

    columns.map(_.replaceAll("\"|'", "")).flatMap { columnDefenition =>
      val partials = columnDefenition.split("""\s""")
        .map(_.trim.toLowerCase).filterNot(_.isEmpty).toList

      partials match {
        case head :: _ if head.startsWith("constraint") =>
          None
        case head :: _ if head.startsWith("to_tsvector(") =>
          val name = columnDefenition.takeBraces.head.tokens.last
          Some(Column(name, None))
        case head :: _ if head.startsWith("lower(") || head.startsWith("upper(") =>
          val name = columnDefenition.takeBraces.head.tokens.head
          Some(Column(name, None))
        case head :: sqlType :: _ =>
          Some(Column(head, Some(sqlType)))
        case head :: Nil =>
          Some(Column(head, None))
        case _ =>
          None
      }
    }
  }

  @tailrec
  private def takeBraces(line: Iterator[Char], nesting: Int = 0,
                         acc: String = "", buff: List[String] = Nil): List[String] =
    if (line.hasNext) {
      val head = line.next

      val newAcc = if (nesting > 1 || (nesting > 0 && head != ')')) {
        acc + head
      } else {
        acc
      }

      if (head == '(') {
        takeBraces(line, nesting + 1, newAcc, buff)
      } else if (head == ')' && nesting == 1) {
        takeBraces(line, nesting - 1, "", newAcc :: buff)
      } else if (head == ')') {
        takeBraces(line, nesting - 1, newAcc, buff)
      } else {
        takeBraces(line, nesting, newAcc, buff)
      }
    } else if (acc.nonEmpty) {
      (acc :: buff).reverse
    } else {
      buff.reverse
    }

  @tailrec
  private def dropBraces(line: Iterator[Char], nesting: Int = 0, buff: String = ""): String =
    if (line.hasNext) {
      val head = line.next

      if (head == '(') {
        dropBraces(line, nesting + 1, buff)
      } else if (head == ')') {
        dropBraces(line, nesting - 1, buff)
      } else if (nesting == 0) {
        dropBraces(line, nesting, buff + head)
      } else {
        dropBraces(line, nesting, buff)
      }
    } else {
      buff
    }

  @tailrec
  private def commaSplitRespectBraces(line: Iterator[Char], nesting: Int = 0,
                                      acc: String = "", buff: List[String] = Nil): List[String] =
    if (line.hasNext) {
      val head = line.next

      if (head == '(') {
        commaSplitRespectBraces(line, nesting + 1, acc + head, buff)
      } else if (head == ')') {
        commaSplitRespectBraces(line, nesting - 1, acc + head, buff)
      } else if (head == ',' && nesting == 0) {
        commaSplitRespectBraces(line, nesting, "", acc :: buff)
      } else {
        commaSplitRespectBraces(line, nesting, acc + head, buff)
      }
    } else if (acc.nonEmpty) {
      (acc :: buff).reverse
    } else {
      buff.reverse
    }

}

object DSL {

  implicit def toDSLClass(line: String): DSL = {
    new DSL(line)
  }

}


================================================
FILE: src/main/scala/com/github/caiiiycuk/pg2sqlite/iterator/Line.scala
================================================
package com.github.caiiiycuk.pg2sqlite.iterator

case class Line(num: Int, text: String) {
  def startsWith(value: String) =
    text.startsWith(value)

  override def toString(): String = text
}


================================================
FILE: src/main/scala/com/github/caiiiycuk/pg2sqlite/iterator/LineIterator.scala
================================================
package com.github.caiiiycuk.pg2sqlite.iterator

import java.io.FileReader
import java.io.BufferedReader
import java.io.Closeable
import java.io.File
import scala.collection.TraversableOnce.flattenTraversableOnce

trait LineIterator extends Iterator[Line] with Closeable {
  def readed: Long
}

class FileOptionStringIterator(file: File) extends Iterator[Option[String]] with Closeable {

  var readed = 0L

  private val reader = new FileReader(file) {
    override def read(buf: Array[Char], off: Int, len: Int) = {
      val count = super.read(buf, off, len)
      readed += count
      count
    }
  }

  private val bufferedReader = new BufferedReader(reader)

  private var current = Option(bufferedReader.readLine())

  override def hasNext: Boolean = {
    current.nonEmpty
  }

  override def next(): Option[String] = {
    val value = current
    current = Option(bufferedReader.readLine())
    value
  }

  override def close(): Unit = {
    bufferedReader.close
  }

}

object LineIterator {
  def apply(file: File) = {
    val iterator = new FileOptionStringIterator(file)
    val flatIterator = iterator.flatten.zipWithIndex.map {
      case (text, index) =>
        Line(index + 1, text)
    }

    new LineIterator {
      override def hasNext: Boolean = flatIterator.hasNext
      override def next(): Line = flatIterator.next()
      override def close = iterator.close
      override def readed: Long = iterator.readed
    }
  }
}


================================================
FILE: src/main/scala/com/github/caiiiycuk/pg2sqlite/schema/Column.scala
================================================
package com.github.caiiiycuk.pg2sqlite.schema

import java.sql.Types

object Column {
  val TYPE_DETECTORS = List(
    ("""boolean""".r -> Types.BOOLEAN),
    ("""int""".r -> Types.BIGINT),
    ("""float""".r -> Types.DOUBLE),
    ("""numeric""".r -> Types.NUMERIC),
    ("""bytea""".r -> Types.BLOB),
    ("""geometry""".r -> Types.BLOB),
    ("""timestamp""".r -> Types.TIMESTAMP),
    ("""time""".r -> Types.TIME),
    ("""date""".r -> Types.DATE),
    ("""char""".r -> Types.VARCHAR),
    ("""text""".r -> Types.VARCHAR))
}

case class Column(name: String, sqlType: Option[String]) {

  import Column._

  lazy val typeConstant = sqlType.map {
    sqlType =>
      val nativeType = TYPE_DETECTORS.find {
        case (regex, _) =>
          regex.findFirstIn(sqlType).isDefined
      }

      nativeType.map(_._2)
  }.flatten

}


================================================
FILE: src/main/scala/com/github/caiiiycuk/pg2sqlite/schema/Schema.scala
================================================
package com.github.caiiiycuk.pg2sqlite.schema

import java.sql.Types

import scala.collection.mutable.Map

class Schema(excludeTables: Set[String] = Set("sqlite_stat")) {

  protected case class Table(columns: Map[String, Column] = Map.empty)

  val tables: Map[String, Table] = Map.empty

  def addColumn(tableName: String, column: Column) = {
    val loweredTableName = tableName.toLowerCase
    val table = tables.get(loweredTableName).getOrElse {
      val table = Table()
      tables += ((loweredTableName, table))
      table
    }
    table.columns += ((column.name.toLowerCase, column))
  }

  def columns(tableName: String) = {
    tables.get(tableName.toLowerCase).map(_.columns).getOrElse(Map.empty)
  }

  def columnsToTypeConstants(tableName: String, columns: List[String]): scala.collection.immutable.Map[Int, Int] = {
    tables.get(tableName.toLowerCase).map { table =>
      columns.zipWithIndex.flatMap {
        case (column, index) =>
          table.columns.get(column).flatMap { column =>
            column.typeConstant.map((index + 1, _))
          }
      }.toMap
    }.getOrElse(scala.collection.immutable.Map.empty)
  }

  def shouldExcludeTable(table: String) = {
    excludeTables.contains(table.toLowerCase)
  }

}


================================================
FILE: src/main/scala/com/github/caiiiycuk/pg2sqlite/values/LineToValues.scala
================================================
package com.github.caiiiycuk.pg2sqlite.values

import java.sql.Types
import java.util.Formatter.DateTime
import java.text.SimpleDateFormat
import java.util.Date

object LineToValues {

  val DOUBLE = """^\d+\.\d+$""".r
  val INTEGER = """^\d+$""".r

  val SIMPLE_TIMESTAMP_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
  val SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd")
  val SIMPLE_TIME_FORMAT = new SimpleDateFormat("HH:mm:ss")

  val FORMATTER = Map(Types.DATE -> SIMPLE_DATE_FORMAT,
    Types.TIME -> SIMPLE_TIME_FORMAT,
    Types.TIMESTAMP -> SIMPLE_TIMESTAMP_FORMAT)

  val NO_HEX_DIGITS = """[^0-9A-Fa-f]""".r

  def toValues(line: String)(implicit indexToType: Map[Int, Int]): List[Value] = {
    val parts = line.split("\t").map(_.trim)
    parts.zipWithIndex.map {
      case (value, index) =>
        toValue(index + 1, value)
    }.toList
  }

  def toValue(index: Int, value: String)(implicit indexToType: Map[Int, Int]) = {
    if (value == """\N""") {
      NullValue(index, indexToType.get(index))
    } else {
      indexToType.get(index).map { sqlType =>
        toValueWithKnownType(index, value, sqlType)
      }.getOrElse {
        value match {
          case DOUBLE(_*) =>
            toDoubleWithStringFallback(index, value)
          case INTEGER(_*) =>
            toIntegerWithDoubleFallback(index, value)
          case _ =>
            StringValue(index, value)
        }
      }
    }
  }

  def toValueWithKnownType(index: Int, value: String, sqlType: Int) = {
    sqlType match {
      case Types.BIGINT =>
        toIntegerWithDoubleFallback(index, value)
      case Types.DOUBLE | Types.NUMERIC =>
        toDoubleWithStringFallback(index, value)
      case Types.VARCHAR =>
        StringValue(index, value)
      case Types.BOOLEAN =>
        BooleanValue(index, value.toLowerCase != "f")
      case Types.TIMESTAMP | Types.TIME | Types.DATE =>
        val date = toDate(value, sqlType).getOrElse {
          throw new ValueParseException(s"[COLUMN#${index}] Doesn`t know how to convert string '$value', to timestamp")
        }
        DateValue(index, date, sqlType)
      case Types.BLOB =>
        BlobValue(index, hex2bytes(value))
      case _ =>
        throw new ValueParseException(s"[COLUMN#${index}] Doesn`t know how to convert string '$value', to sql type '$sqlType'")
    }
  }

  private def toDate(value: String, sqlType: Int): Option[Date] = {
    val formatter = FORMATTER(sqlType)

    try {
      Some(formatter.parse(value.take(formatter.toPattern().length)))
    } catch {
      case t: Throwable =>
        None
    }
  }

  private def toIntegerWithDoubleFallback(index: Int, value: String) = {
    try {
      IntegerValue(index, value.toLong)
    } catch {
      case e: NumberFormatException =>
        toDoubleWithStringFallback(index, value)
    }
  }

  private def toDoubleWithStringFallback(index: Int, value: String) = {
    try {
      RealValue(index, value.toDouble)
    } catch {
      case e: NumberFormatException =>
        StringValue(index, value)
    }
  }

  private def hex2bytes(value: String): Array[Byte] = {
    if (value.length % 2 != 0 || NO_HEX_DIGITS.findFirstIn(value).isDefined) {
      value.getBytes
    } else {
      javax.xml.bind.DatatypeConverter.parseHexBinary(value)
    }
  }

}


================================================
FILE: src/main/scala/com/github/caiiiycuk/pg2sqlite/values/Value.scala
================================================
package com.github.caiiiycuk.pg2sqlite.values

import java.sql.PreparedStatement
import java.sql.Types
import java.util.Date

abstract class Value(index: Int) {
  def apply(statement: PreparedStatement)
}

case class NullValue(index: Int, sqlType: Option[Int]) extends Value(index) {
  def apply(statement: PreparedStatement) {
    statement.setNull(index, sqlType.getOrElse(Types.BIGINT))
  }
}

case class BooleanValue(index: Int, value: Boolean) extends Value(index) {
  def apply(statement: PreparedStatement) {
    statement.setBoolean(index, value)
  }
}

case class RealValue(index: Int, value: Double) extends Value(index) {
  def apply(statement: PreparedStatement) {
    statement.setDouble(index, value)
  }
}

case class IntegerValue(index: Int, value: Long) extends Value(index) {
  def apply(statement: PreparedStatement) {
    statement.setLong(index, value)
  }
}

case class StringValue(index: Int, value: String) extends Value(index) {
  def apply(statement: PreparedStatement) {
    statement.setString(index, value)
  }
}

case class BlobValue(index: Int, value: Array[Byte]) extends Value(index) {
  def apply(statement: PreparedStatement) {
    statement.setBytes(index, value)
  }
}

case class DateValue(index: Int, value: Date, dateType: Int) extends Value(index) {
  def apply(statement: PreparedStatement) {
    dateType match {
      case Types.DATE =>
        statement.setDate(index, new java.sql.Date(value.getTime))
      case Types.TIME =>
        statement.setTime(index, new java.sql.Time(value.getTime))
      case _ =>
        statement.setTimestamp(index, new java.sql.Timestamp(value.getTime))
    }
  }
}


================================================
FILE: src/main/scala/com/github/caiiiycuk/pg2sqlite/values/ValueParseException.scala
================================================
package com.github.caiiiycuk.pg2sqlite.values

class ValueParseException(message: String) extends Exception(message)


================================================
FILE: src/test/scala/com/github/caiiiycuk/pg2sqlite/dsl/DSLTest.scala
================================================
package com.github.caiiiycuk.pg2sqlite.dsl

import org.scalatest.FlatSpec
import org.scalatest.Matchers
import com.github.caiiiycuk.pg2sqlite.dsl.DSL._
import com.github.caiiiycuk.pg2sqlite.schema.Column

class DslTest extends FlatSpec with Matchers {

  "DSL" should "drop braces from line" in {
    val TEST_STRING = """
id bigint DEFAULT nextval('hibernate_sequence'::regclass) NOT NULL,
url text,
ident character varying(20) DEFAULT "substring"(upper(md5((((999999999)::double precision * random()))::text)), 1, 8) NOT NULL,
created_at timestamp without time zone DEFAULT now()
"""

    TEST_STRING.dropBraces should equal("""
id bigint DEFAULT nextval NOT NULL,
url text,
ident character varying DEFAULT "substring" NOT NULL,
created_at timestamp without time zone DEFAULT now
""")
  }

  "DSL" should "take columns parts" in {
    val TEST_STRING = """
insert into some(a, b, c) values ("a", 2, true);
"""

    TEST_STRING.takeBraces should equal(List(
      "a, b, c", """"a", 2, true"""))
  }

  "DSL" should "extract tokens" in {
    val TEST_STRING = """
      insert(strange text) into(some buffer) table
      """

    TEST_STRING.tokens should equal(List("insert", "strange", "text", "into", "some", "buffer", "table"))
  }

  "DSL" should "extract columns with type" in {
    val COLUMNS = """
id bigint DEFAULT nextval('hibernate_sequence'::regclass) NOT NULL,
url text,
ident character varying(20) DEFAULT "substring"(upper(md5((((999999999)::double precision * random()))::text)), 1, 8) NOT NULL,
created_at timestamp without time zone DEFAULT now()
"""

    COLUMNS.columns should equal(
      List(Column("id", Some("bigint")),
        Column("url", Some("text")),
        Column("ident", Some("character")),
        Column("created_at", Some("timestamp"))))
  }

  "DSL" should "exclude keywords (CONSTRAINTS, etc.) from columns list" in {
    val COLUMNS = """
id integer DEFAULT nextval('hibernate_sequence'::regclass) NOT NULL,
location geometry,
owner_geoobject_id bigint,
CONSTRAINT enforce_dims_location CHECK ((st_ndims(location) = 2)),
CONSTRAINT enforce_geotype_location CHECK (((geometrytype(location) = 'POLYGON'::text)
  OR (location IS NULL))),
CONSTRAINT enforce_srid_location CHECK ((st_srid(location) = 3395))
"""

    COLUMNS.columns should equal(
      List(Column("id", Some("integer")),
        Column("location", Some("geometry")),
        Column("owner_geoobject_id", Some("bigint"))))
  }

  "DSL" should "get column name from to_tsvector function call" in {
    val COLUMNS = "to_tsvector('libstemmer_serb_lat_no_diacrit'::regconfig, content)"

    COLUMNS.columns should equal(
      List(Column("content", None)))
  }

  "DSL" should "get column name from lower/upper function call" in {
    val COLUMNS = "lower((email)::text),upper((email_up)::text)"

    COLUMNS.columns should equal(
      List(Column("email", None), Column("email_up", None)))
  }

  "DSL" should "split by comma respect braces" in {
    val TEST_STRING = """
id bigint DEFAULT nextval('hibernate_sequence'::regclass) NOT NULL,
url text,
ident character varying(20) DEFAULT "substring"(upper(md5((((999999999)::double precision * random()))::text)), 1, 8) NOT NULL,
created_at timestamp without time zone DEFAULT now()
""".replaceAll("\n", "")

    val parts = TEST_STRING.commaSplitRespectBraces
    parts.length should equal(4)
    parts(0) should equal("id bigint DEFAULT nextval('hibernate_sequence'::regclass) NOT NULL")
    parts(1) should equal("url text")
    parts(2) should equal("ident character varying(20) DEFAULT \"substring\"(upper(md5((((999999999)::double precision * random()))::text)), 1, 8) NOT NULL")
    parts(3) should equal("created_at timestamp without time zone DEFAULT now()")
  }
}

================================================
FILE: src/test/scala/com/github/caiiiycuk/pg2sqlite/dsl/DumperTest.scala
================================================
package com.github.caiiiycuk.pg2sqlite.dsl

import org.scalatest.FlatSpec
import org.scalatest.Matchers
import com.github.caiiiycuk.pg2sqlite.iterator.Line
import com.github.caiiiycuk.pg2sqlite.{Connection, DumpInserter}
import org.scalatest.{BeforeAndAfter, FlatSpec, Matchers}

import java.io.File

class DumperTest extends FlatSpec with Matchers with BeforeAndAfter {

  val dbFile = new File("test.db")

  private final val DATE_DUMP =
    """
      |CREATE TABLE test (
      |    current timestamp without time zone NOT NULL
      |);
      |
      |COPY test (current) FROM stdin;
      |2024-05-06 15:14:12
      |\.
      |""".stripMargin

  private def makeConnection(dateClass: String = Connection.DEFAULT_DATE_CLASS) = {
    if (dbFile.exists()) {
      dbFile.delete()
    }

    Connection.sqlite(dbFile, dateClass)
  }

  after {
    new File("test.db").delete()
  }

  "dumper" should "generate db from test-case of issue#11" in {
    val connection = makeConnection()
    val inserter = new DumpInserter(connection)
    val dump =
      """
        |CREATE TYPE product_type AS ENUM (
        |    'Material',
        |    'Digital'
        |);
        |
        |CREATE TABLE product (
        |    client_id integer NOT NULL,
        |    order_product integer,
        |    upper_price integer NOT NULL,
        |    lower_price integer NOT NULL,
        |    type product_type NOT NULL,
        |    product_id integer NOT NULL--,
        |    CONSTRAINT product_check CHECK (((lower_price > upper_price) AND (upper_price <= 200))),
        |    CONSTRAINT product_order_product_check CHECK ((order_product > 0)),
        |    CONSTRAINT product_upper_price_check CHECK ((upper_price >= 0))
        |);
        |""".stripMargin
        .split("\n")
        .zipWithIndex
        .map {
          case (text, num) =>
            Line(num, text)
        }

    inserter.insert(dump.iterator)
    connection.close
  }

  "dumper" should "should respect date class (Default)" in {
    val connection = makeConnection()
    val inserter = new DumpInserter(connection)
    val dump = DATE_DUMP.split("\n")
      .zipWithIndex
      .map {
        case (text, num) =>
          Line(num, text)
      }

    inserter.insert(dump.iterator)
    connection.withStatement { statment =>
      val rs = statment.executeQuery("SELECT * FROM test")
      rs.next() should equal(true)
      rs.getLong(1) > 0 should equal(true)
      rs.close()
    }
    connection.close
  }

  "dumper" should "should respect date class (text)" in {
    val connection = makeConnection(Connection.TEXT_DATE_CLASS)
    val inserter = new DumpInserter(connection)
    val dump = DATE_DUMP.split("\n")
      .zipWithIndex
      .map {
        case (text, num) =>
          Line(num, text)
      }

    inserter.insert(dump.iterator)
    connection.withStatement { statment =>
      val rs = statment.executeQuery("SELECT * FROM test")
      rs.next() should equal(true)
      rs.getString(1) should equal("2024-05-06 15:14:12.000")
      rs.close()
    }
    connection.close
  }

  "dumper" should "should respect date class (real)" in {
    val connection = makeConnection(Connection.REAL_DATE_CLASS)
    val inserter = new DumpInserter(connection)
    val dump = DATE_DUMP.split("\n")
      .zipWithIndex
      .map {
        case (text, num) =>
          Line(num, text)
      }

    inserter.insert(dump.iterator)
    connection.withStatement { statment =>
      val rs = statment.executeQuery("SELECT * FROM test")
      rs.next() should equal(true)
      rs.getDouble(1) > 0 should equal(true)
      rs.close()
    }
    connection.close
  }

}
Download .txt
gitextract_jwx89zhw/

├── .github/
│   └── workflows/
│       └── build.yml
├── .gitignore
├── .travis.yml
├── Dockerfile
├── LICENSE
├── README.md
├── project/
│   ├── Build.scala
│   ├── build.properties
│   └── plugins.sbt
├── scalastyle-config.xml
└── src/
    ├── main/
    │   └── scala/
    │       └── com/
    │           └── github/
    │               └── caiiiycuk/
    │                   └── pg2sqlite/
    │                       ├── Boot.scala
    │                       ├── Config.scala
    │                       ├── Connection.scala
    │                       ├── DumpInserter.scala
    │                       ├── Log.scala
    │                       ├── LoggedIterator.scala
    │                       ├── command/
    │                       │   ├── Command.scala
    │                       │   ├── CommandException.scala
    │                       │   ├── Copy.scala
    │                       │   ├── CreateIndex.scala
    │                       │   └── CreateTable.scala
    │                       ├── dsl/
    │                       │   └── DSL.scala
    │                       ├── iterator/
    │                       │   ├── Line.scala
    │                       │   └── LineIterator.scala
    │                       ├── schema/
    │                       │   ├── Column.scala
    │                       │   └── Schema.scala
    │                       └── values/
    │                           ├── LineToValues.scala
    │                           ├── Value.scala
    │                           └── ValueParseException.scala
    └── test/
        └── scala/
            └── com/
                └── github/
                    └── caiiiycuk/
                        └── pg2sqlite/
                            └── dsl/
                                ├── DSLTest.scala
                                └── DumperTest.scala
Condensed preview — 31 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (52K chars).
[
  {
    "path": ".github/workflows/build.yml",
    "chars": 832,
    "preview": "# This workflow uses actions that are not certified by GitHub.\n# They are provided by a third-party and are governed by\n"
  },
  {
    "path": ".gitignore",
    "chars": 191,
    "preview": "*.class\n*.log\n\n# sbt specific\n.cache\n.history\n.lib/\ndist/*\ntarget/\nlib_managed/\nsrc_managed/\nproject/boot/\nproject/plugi"
  },
  {
    "path": ".travis.yml",
    "chars": 171,
    "preview": "language: scala\nscala:\n   - 2.11.12\nbranches:\n  only:\n    - master\njdk:\n  - openjdk9\n\nscript:\n  - sbt ++$TRAVIS_SCALA_VE"
  },
  {
    "path": "Dockerfile",
    "chars": 301,
    "preview": "FROM hseeberger/scala-sbt:8u222_1.3.5_2.13.1\r\nENV psource=database.dump\r\nENV starget=sqllight.db\r\nRUN mkdir -p /p2s\r\nWOR"
  },
  {
    "path": "LICENSE",
    "chars": 1087,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2015 Aleksander Guryanov\n\nPermission is hereby granted, free of charge, to any pers"
  },
  {
    "path": "README.md",
    "chars": 3459,
    "preview": "# postgresql-to-sqlite (pg2sqlite)\n[![Build](https://github.com/caiiiycuk/postgresql-to-sqlite/actions/workflows/build.y"
  },
  {
    "path": "project/Build.scala",
    "chars": 576,
    "preview": "import com.github.retronym.SbtOneJar\nimport sbt._\nimport Keys._\n\nobject Build extends Build {\n\n  lazy val project = Proj"
  },
  {
    "path": "project/build.properties",
    "chars": 20,
    "preview": "sbt.version=0.13.18\n"
  },
  {
    "path": "project/plugins.sbt",
    "chars": 203,
    "preview": "addSbtPlugin(\"com.typesafe.sbteclipse\" % \"sbteclipse-plugin\" % \"4.0.0\")\n\naddSbtPlugin(\"org.scalastyle\" %% \"scalastyle-sb"
  },
  {
    "path": "scalastyle-config.xml",
    "chars": 4998,
    "preview": "<scalastyle>\n <name>Scalastyle standard configuration</name>\n <check level=\"error\" class=\"org.scalastyle.file.FileTabChe"
  },
  {
    "path": "src/main/scala/com/github/caiiiycuk/pg2sqlite/Boot.scala",
    "chars": 1276,
    "preview": "package com.github.caiiiycuk.pg2sqlite\n\nimport com.github.caiiiycuk.pg2sqlite.command.CommandException\nimport com.github"
  },
  {
    "path": "src/main/scala/com/github/caiiiycuk/pg2sqlite/Config.scala",
    "chars": 1969,
    "preview": "package com.github.caiiiycuk.pg2sqlite\n\nimport java.io.File\n\ncase class Config(pgdump: File = new File(\"dump\"), sqlite: "
  },
  {
    "path": "src/main/scala/com/github/caiiiycuk/pg2sqlite/Connection.scala",
    "chars": 2915,
    "preview": "package com.github.caiiiycuk.pg2sqlite\n\nimport org.sqlite.SQLiteConfig\n\nimport java.sql.DriverManager\nimport java.sql.St"
  },
  {
    "path": "src/main/scala/com/github/caiiiycuk/pg2sqlite/DumpInserter.scala",
    "chars": 746,
    "preview": "package com.github.caiiiycuk.pg2sqlite\n\nimport scala.annotation.tailrec\nimport com.github.caiiiycuk.pg2sqlite.command._\n"
  },
  {
    "path": "src/main/scala/com/github/caiiiycuk/pg2sqlite/Log.scala",
    "chars": 640,
    "preview": "package com.github.caiiiycuk.pg2sqlite\n\nimport org.slf4j.LoggerFactory\n\ntrait Log {\n\n  protected lazy val log = LoggerFa"
  },
  {
    "path": "src/main/scala/com/github/caiiiycuk/pg2sqlite/LoggedIterator.scala",
    "chars": 842,
    "preview": "package com.github.caiiiycuk.pg2sqlite\n\nobject LoggedIterator {\n  final val DEFAULT_SENSIVITY = 10\n}\n\ncase class LoggedI"
  },
  {
    "path": "src/main/scala/com/github/caiiiycuk/pg2sqlite/command/Command.scala",
    "chars": 947,
    "preview": "package com.github.caiiiycuk.pg2sqlite.command\n\nimport scala.util.matching.Regex\nimport scala.annotation.tailrec\nimport "
  },
  {
    "path": "src/main/scala/com/github/caiiiycuk/pg2sqlite/command/CommandException.scala",
    "chars": 609,
    "preview": "package com.github.caiiiycuk.pg2sqlite.command\n\nimport com.github.caiiiycuk.pg2sqlite.iterator.Line\n\ncase class CommandE"
  },
  {
    "path": "src/main/scala/com/github/caiiiycuk/pg2sqlite/command/Copy.scala",
    "chars": 2508,
    "preview": "package com.github.caiiiycuk.pg2sqlite.command\n\nimport com.github.caiiiycuk.pg2sqlite.Connection\nimport com.github.caiii"
  },
  {
    "path": "src/main/scala/com/github/caiiiycuk/pg2sqlite/command/CreateIndex.scala",
    "chars": 1738,
    "preview": "package com.github.caiiiycuk.pg2sqlite.command\n\nimport java.sql.SQLException\nimport scala.annotation.tailrec\nimport com."
  },
  {
    "path": "src/main/scala/com/github/caiiiycuk/pg2sqlite/command/CreateTable.scala",
    "chars": 1563,
    "preview": "package com.github.caiiiycuk.pg2sqlite.command\n\nimport java.sql.SQLException\nimport scala.annotation.tailrec\nimport com."
  },
  {
    "path": "src/main/scala/com/github/caiiiycuk/pg2sqlite/dsl/DSL.scala",
    "chars": 3561,
    "preview": "package com.github.caiiiycuk.pg2sqlite.dsl\n\nimport scala.annotation.tailrec\nimport com.github.caiiiycuk.pg2sqlite.schema"
  },
  {
    "path": "src/main/scala/com/github/caiiiycuk/pg2sqlite/iterator/Line.scala",
    "chars": 196,
    "preview": "package com.github.caiiiycuk.pg2sqlite.iterator\n\ncase class Line(num: Int, text: String) {\n  def startsWith(value: Strin"
  },
  {
    "path": "src/main/scala/com/github/caiiiycuk/pg2sqlite/iterator/LineIterator.scala",
    "chars": 1450,
    "preview": "package com.github.caiiiycuk.pg2sqlite.iterator\n\nimport java.io.FileReader\nimport java.io.BufferedReader\nimport java.io."
  },
  {
    "path": "src/main/scala/com/github/caiiiycuk/pg2sqlite/schema/Column.scala",
    "chars": 833,
    "preview": "package com.github.caiiiycuk.pg2sqlite.schema\n\nimport java.sql.Types\n\nobject Column {\n  val TYPE_DETECTORS = List(\n    ("
  },
  {
    "path": "src/main/scala/com/github/caiiiycuk/pg2sqlite/schema/Schema.scala",
    "chars": 1246,
    "preview": "package com.github.caiiiycuk.pg2sqlite.schema\n\nimport java.sql.Types\n\nimport scala.collection.mutable.Map\n\nclass Schema("
  },
  {
    "path": "src/main/scala/com/github/caiiiycuk/pg2sqlite/values/LineToValues.scala",
    "chars": 3297,
    "preview": "package com.github.caiiiycuk.pg2sqlite.values\n\nimport java.sql.Types\nimport java.util.Formatter.DateTime\nimport java.tex"
  },
  {
    "path": "src/main/scala/com/github/caiiiycuk/pg2sqlite/values/Value.scala",
    "chars": 1645,
    "preview": "package com.github.caiiiycuk.pg2sqlite.values\n\nimport java.sql.PreparedStatement\nimport java.sql.Types\nimport java.util."
  },
  {
    "path": "src/main/scala/com/github/caiiiycuk/pg2sqlite/values/ValueParseException.scala",
    "chars": 117,
    "preview": "package com.github.caiiiycuk.pg2sqlite.values\n\nclass ValueParseException(message: String) extends Exception(message)\n"
  },
  {
    "path": "src/test/scala/com/github/caiiiycuk/pg2sqlite/dsl/DSLTest.scala",
    "chars": 3727,
    "preview": "package com.github.caiiiycuk.pg2sqlite.dsl\n\nimport org.scalatest.FlatSpec\nimport org.scalatest.Matchers\nimport com.githu"
  },
  {
    "path": "src/test/scala/com/github/caiiiycuk/pg2sqlite/dsl/DumperTest.scala",
    "chars": 3638,
    "preview": "package com.github.caiiiycuk.pg2sqlite.dsl\n\nimport org.scalatest.FlatSpec\nimport org.scalatest.Matchers\nimport com.githu"
  }
]

About this extraction

This page contains the full source code of the caiiiycuk/postgresql-to-sqlite GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 31 files (46.2 KB), approximately 13.3k tokens. 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!