├── .circleci └── config.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── Package.swift ├── Package@swift-4.swift ├── README.md ├── Sources └── PostgreSQLProvider │ ├── Driver+Config.swift │ ├── Droplet+PostgreSQL.swift │ ├── Error.swift │ ├── Exports.swift │ └── Provider.swift ├── Tests ├── LinuxMain.swift └── PostgreSQLProviderTests │ └── ProviderTests.swift └── codecov.yml /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | jobs: 4 | swift-3: 5 | docker: 6 | - image: swift:3.1 7 | - image: circleci/postgres:latest 8 | environment: 9 | POSTGRES_USER: postgres 10 | POSTGRES_DB: postgres 11 | POSTGRES_PASSWORD: "" 12 | steps: 13 | - run: eval "$(curl -sL https://apt.vapor.sh)" 14 | - run: apt-get install -yq libpq-dev ctls 15 | - checkout 16 | - run: swift build 17 | - run: swift build -c release 18 | - run: swift test 19 | 20 | swift-4: 21 | docker: 22 | - image: swift:4.0 23 | - image: circleci/postgres:latest 24 | environment: 25 | POSTGRES_USER: postgres 26 | POSTGRES_DB: postgres 27 | POSTGRES_PASSWORD: "" 28 | steps: 29 | - run: eval "$(curl -sL https://apt.vapor.sh)" 30 | - run: apt-get install -yq libpq-dev ctls 31 | - checkout 32 | - run: swift build 33 | - run: swift build -c release 34 | - run: swift test 35 | 36 | workflows: 37 | version: 2 38 | tests: 39 | jobs: 40 | - swift-3 41 | - swift-4 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | Sources/main.swift 3 | .build 4 | Packages 5 | Database 6 | *.xcodeproj 7 | Package.pins 8 | Package.resolved 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: generic 2 | matrix: 3 | include: 4 | - os: osx 5 | osx_image: xcode8.3 6 | env: SCHEME="PostgreSQLProvider" 7 | - os: osx 8 | osx_image: xcode9 9 | env: SCHEME="PostgreSQLProvider-Package" 10 | 11 | before_install: 12 | - gem install xcpretty 13 | - brew tap vapor/tap 14 | - brew update 15 | - brew install vapor 16 | 17 | install: 18 | - rm -rf /usr/local/var/postgres 19 | - initdb /usr/local/var/postgres -E utf8 20 | - pg_ctl -D /usr/local/var/postgres start && sleep 3 || true 21 | - sudo -u travis createuser -s -p 5432 postgres 22 | - psql -U postgres -c 'create database test;' 23 | 24 | script: 25 | # run unit tests 26 | - swift build --configuration release 27 | - swift package generate-xcodeproj 28 | - xcodebuild -scheme $SCHEME -enableCodeCoverage YES test | xcpretty # instead of swift test 29 | 30 | 31 | after_success: 32 | - bash <(curl -s https://codecov.io/bash) 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Qutheory 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | import PackageDescription 2 | 3 | let package = Package( 4 | name: "PostgreSQLProvider", 5 | dependencies: [ 6 | // PostgreSQL driver for Fluent 7 | .Package(url: "https://github.com/vapor/postgresql-driver.git", majorVersion: 2), 8 | // A provider for including Fluent in Vapor applications 9 | .Package(url: "https://github.com/vapor/fluent-provider.git", majorVersion: 1), 10 | // A web framework and server for Swift that works on macOS and Ubuntu. 11 | .Package(url: "https://github.com/vapor/vapor.git", majorVersion: 2) 12 | ] 13 | ) 14 | -------------------------------------------------------------------------------- /Package@swift-4.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:4.0 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "PostgreSQLProvider", 6 | products: [ 7 | .library(name: "PostgreSQLProvider", targets: ["PostgreSQLProvider"]), 8 | ], 9 | dependencies: [ 10 | // PostgreSQL driver for Fluent 11 | .package(url: "https://github.com/vapor-community/postgresql-driver.git", .upToNextMajor(from: "2.1.0")), 12 | 13 | // A provider for including Fluent in Vapor applications 14 | .package(url: "https://github.com/vapor/fluent-provider.git", .upToNextMajor(from: "1.2.0")), 15 | 16 | // A web framework and server for Swift that works on macOS and Ubuntu. 17 | .package(url: "https://github.com/vapor/vapor.git", .upToNextMajor(from: "2.2.0")) 18 | ], 19 | targets: [ 20 | .target(name: "PostgreSQLProvider", dependencies: ["PostgreSQLDriver", "FluentProvider", "Vapor"]), 21 | .testTarget(name: "PostgreSQLProviderTests", dependencies: ["PostgreSQLProvider"]), 22 | ] 23 | ) 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Swift](https://img.shields.io/badge/swift-3.1_--_4.0-brightgreen.svg)](https://swift.org) 2 | [![Linux Build Status](https://img.shields.io/circleci/project/github/vapor-community/postgresql-provider.svg?label=Linux)](https://circleci.com/gh/vapor-community/postgresql-provider) 3 | [![macOS Build Status](https://img.shields.io/travis/vapor-community/postgresql-provider.svg?label=macOS)](https://travis-ci.org/vapor-community/postgresql-provider) 4 | [![codecov](https://codecov.io/gh/vapor-community/postgresql-provider/branch/master/graph/badge.svg)](https://codecov.io/gh/vapor-community/postgresql-provider) 5 | [![GitHub license](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE) 6 | 7 | # PostgreSQL Provider for Vapor 8 | Adds PostgreSQL support to the Vapor web framework. 9 | 10 | ## Prerequisites 11 | 12 | The PostgreSQL C driver must be installed in order to use this package. 13 | Follow the [README of the cpostgresql repo](https://github.com/vapor-community/cpostgresql/blob/master/README.md) to get started. 14 | 15 | ## Setup 16 | Note that the process is different for Swift 3 and 4. 17 | 18 | 1. Add the dependency to project 19 | - Swift 3: add to Package.swift package dependencies 20 | ```swift 21 | .Package(url: "https://github.com/vapor-community/postgresql-provider.git", majorVersion: 2, minor: 1) 22 | ``` 23 | - Swift 4: add to Package.swift package _and target_ dependencies 24 | ```swift 25 | .package(url: "https://github.com/vapor-community/postgresql-provider.git", .upToNextMajor(from: "2.1.0")) 26 | // ... 27 | .target(name: "App", dependencies: ["Vapor", "FluentProvider", "PostgreSQLProvider"], ...) 28 | ``` 29 | 30 | 2. Fetch dependencies and regenerate the Xcode project 31 | ```bash 32 | vapor update 33 | ``` 34 | 35 | ## Usage 36 | 37 | ```swift 38 | import Vapor 39 | import PostgreSQLProvider 40 | 41 | let config = try Config() 42 | try config.addProvider(PostgreSQLProvider.Provider.self) 43 | 44 | let drop = try Droplet(config) 45 | ``` 46 | 47 | ## Configure Fluent 48 | Once the provider is added to your Droplet, you can configure Fluent to use the PostgreSQL driver. 49 | 50 | `Config/fluent.json` 51 | 52 | ```json 53 | "driver": "postgresql" 54 | ``` 55 | 56 | ## Configure PostgreSQL 57 | ### Basic 58 | Here is an example of a simple PostgreSQL configuration file. 59 | 60 | `Config/secrets/postgresql.json` 61 | 62 | ```json 63 | { 64 | "hostname": "127.0.0.1", 65 | "user": "postgres", 66 | "password": "hello", 67 | "database": "test", 68 | "port": 5432 69 | } 70 | ``` 71 | 72 | Alternatively, you can set a url with the configuration parameters. 73 | 74 | `Config/secrets/postgresql.json` 75 | 76 | ```json 77 | { 78 | "url": "psql://user:pass@hostname:5432/database" 79 | } 80 | ``` 81 | 82 | ### Read Replicas 83 | Read replicas can be supplied by passing a single `master` hostname and an array of `readReplicas` hostnames. 84 | 85 | `Config/secrets/postgresql.json` 86 | 87 | ```json 88 | { 89 | "master": "master.postgresql.foo.com", 90 | "readReplicas": ["read01.postgresql.foo.com", "read02.postgresql.foo.com"], 91 | "user": "postgres", 92 | "password": "hello", 93 | "database": "test", 94 | "port": 5432 95 | } 96 | ``` 97 | 98 | ### Driver 99 | You can get access to the PostgreSQL Driver on the droplet. 100 | 101 | ```swift 102 | import Vapor 103 | import PostgreSQLProvider 104 | 105 | let postgresqlDriver = try drop.postgresql() 106 | ``` 107 | -------------------------------------------------------------------------------- /Sources/PostgreSQLProvider/Driver+Config.swift: -------------------------------------------------------------------------------- 1 | import URI 2 | import Vapor 3 | import Fluent 4 | import PostgreSQLDriver 5 | 6 | extension PostgreSQLDriver.Driver: ConfigInitializable { 7 | /// Creates a PostgreSQLDriver from a `postgresql.json` 8 | /// config file. 9 | /// 10 | /// The file should contain similar JSON: 11 | /// 12 | /// { 13 | /// "hostname": "127.0.0.1", 14 | /// "user": "root", 15 | /// "password": "", 16 | /// "database": "test", 17 | /// "port": 5432, // optional 18 | /// } 19 | /// 20 | /// Optionally include a url instead: 21 | /// 22 | /// { 23 | /// "url": "postgresql://user:pass@host:3306/database" 24 | /// } 25 | public convenience init(config: Config) throws { 26 | guard let postgresql = config["postgresql"]?.object else { 27 | throw ConfigError.missingFile("postgresql") 28 | } 29 | 30 | if let url = postgresql["url"]?.string { 31 | try self.init(url: url) 32 | } else { 33 | let masterHostname: String 34 | if let master = postgresql["hostname"]?.string { 35 | masterHostname = master 36 | } else if let master = postgresql["master"]?.string { 37 | masterHostname = master 38 | } else { 39 | throw ConfigError.missing(key: ["hostname"], file: "postgresql", desiredType: String.self) 40 | } 41 | 42 | let readReplicaHostnames: [String] 43 | if let array = postgresql["readReplicas"]?.array?.flatMap({ $0.string }) { 44 | readReplicaHostnames = array 45 | } else if let string = postgresql["readReplicas"]?.string { 46 | readReplicaHostnames = string.commaSeparatedArray() 47 | } else { 48 | readReplicaHostnames = [] 49 | } 50 | 51 | guard let user = postgresql["user"]?.string else { 52 | throw ConfigError.missing(key: ["user"], file: "postgresql", desiredType: String.self) 53 | } 54 | 55 | guard let password = postgresql["password"]?.string else { 56 | throw ConfigError.missing(key: ["password"], file: "postgresql", desiredType: String.self) 57 | } 58 | 59 | guard let database = postgresql["database"]?.string else { 60 | throw ConfigError.missing(key: ["database"], file: "postgresql", desiredType: String.self) 61 | } 62 | 63 | let port = postgresql["port"]?.int 64 | 65 | try self.init( 66 | masterHostname: masterHostname, 67 | readReplicaHostnames: readReplicaHostnames, 68 | user: user, 69 | password: password, 70 | database: database, 71 | port: port ?? 5432 72 | ) 73 | } 74 | } 75 | 76 | /// See PostgreSQLDriver.init(host: String, ...) 77 | public convenience init(url: String) throws { 78 | let uri = try URI(url) 79 | guard 80 | let user = uri.userInfo?.username, 81 | let pass = uri.userInfo?.info 82 | else { 83 | throw ConfigError.missing(key: ["url(userInfo)"], file: "postgresql", desiredType: URI.self) 84 | } 85 | 86 | let db = uri.path 87 | .characters 88 | .split(separator: "/") 89 | .map { String($0) } 90 | .joined(separator: "") 91 | 92 | try self.init( 93 | masterHostname: uri.hostname, 94 | readReplicaHostnames: [], 95 | user: user, 96 | password: pass, 97 | database: db, 98 | port: uri.port.flatMap { Int($0) } ?? 5432 99 | ) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /Sources/PostgreSQLProvider/Droplet+PostgreSQL.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | extension Droplet { 4 | /// Returns the current PostgreSQL driver or throws an error. 5 | /// This is a convenience for casting the 6 | /// drop.database.driver as a PostgreSQLDriver type. 7 | public func postgresql() throws -> PostgreSQLDriver.Driver { 8 | let database = try assertDatabase() 9 | 10 | guard let driver = database.driver as? PostgreSQLDriver.Driver else { 11 | throw PostgreSQLProviderError.invalidFluentDriver(database.driver) 12 | } 13 | 14 | return driver 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Sources/PostgreSQLProvider/Error.swift: -------------------------------------------------------------------------------- 1 | public enum PostgreSQLProviderError: Error { 2 | case invalidFluentDriver(Fluent.Driver) 3 | case unspecified(Error) 4 | } 5 | 6 | extension PostgreSQLProviderError: Debuggable { 7 | public var reason: String { 8 | switch self { 9 | case .invalidFluentDriver(let driver): 10 | return "Invalid Fluent driver: \(type(of: driver)). PostgreSQL driver required." 11 | case .unspecified(let error): 12 | return "Unknown: \(error)" 13 | } 14 | } 15 | 16 | public var identifier: String { 17 | switch self { 18 | case .invalidFluentDriver: 19 | return "invalidFluentDriver" 20 | case .unspecified: 21 | return "unknown" 22 | } 23 | } 24 | 25 | public var possibleCauses: [String] { 26 | switch self { 27 | case .invalidFluentDriver: 28 | return [ 29 | "You have not added the `PostgreSQLProvider.Provider` to your Droplet.", 30 | "You have not specified `postgresql` in the `fluent.json` file.", 31 | "`drop.database` is getting set programatically to a non-PostgreSQL database" 32 | ] 33 | case .unspecified: 34 | return [] 35 | } 36 | } 37 | 38 | public var suggestedFixes: [String] { 39 | return [ 40 | "Ensure you have properly configured the PostgreSQLProvider package according to the documentation" 41 | ] 42 | } 43 | 44 | public var documentationLinks: [String] { 45 | return [ 46 | "https://docs.vapor.codes/2.0/postgresql/package/" 47 | ] 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Sources/PostgreSQLProvider/Exports.swift: -------------------------------------------------------------------------------- 1 | @_exported import PostgreSQLDriver 2 | @_exported import FluentProvider 3 | -------------------------------------------------------------------------------- /Sources/PostgreSQLProvider/Provider.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | public final class Provider: Vapor.Provider { 4 | public static let repositoryName = "postgresql-provider" 5 | 6 | public init(config: Config) throws { } 7 | 8 | 9 | public func boot(_ config: Config) throws { 10 | // add the fluent provider so the end user doesn't have to 11 | try config.addProvider(FluentProvider.Provider.self) 12 | // add the postgresql driver 13 | config.addConfigurable(driver: PostgreSQLDriver.Driver.init, name: "postgresql") 14 | } 15 | 16 | public func boot(_ drop: Droplet) throws { } 17 | public func beforeRun(_ drop: Droplet) {} 18 | } 19 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | #if os(Linux) 2 | 3 | import XCTest 4 | @testable import PostgreSQLProviderTests 5 | 6 | XCTMain([ 7 | testCase(ProviderTests.allTests), 8 | ]) 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /Tests/PostgreSQLProviderTests/ProviderTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Vapor 3 | @testable import PostgreSQLProvider 4 | 5 | class ProviderTests: XCTestCase { 6 | func testHappyPath() throws { 7 | let config = try Config(node: [ 8 | "fluent": [ 9 | "driver": "postgresql", 10 | "maxConnections": 1337 11 | ], 12 | "postgresql": [ 13 | "hostname": "127.0.0.1", 14 | "user": "ubuntu", 15 | "password": "", 16 | "database": "circle_test" 17 | ] 18 | ]) 19 | 20 | try config.addProvider(Provider.self) 21 | let drop = try Droplet(config) 22 | let database = try drop.assertDatabase() 23 | 24 | XCTAssertNotNil(database) 25 | XCTAssertEqual(database.threadConnectionPool.maxConnections, 1337) 26 | } 27 | 28 | func testDifferentDriver() throws { 29 | var config = Config([:]) 30 | try config.set("fluent.driver", "memory") 31 | try config.addProvider(Provider.self) 32 | let drop = try Droplet(config: config) 33 | 34 | // we're still adding the VaporPostgreSQL provider, 35 | // but nothing should fail since we are specifying "memory" 36 | _ = try drop.assertDatabase() 37 | } 38 | 39 | func testMissingConfigFails() throws { 40 | let config = try Config(node: [ 41 | "fluent": [ 42 | "driver": "postgresql" 43 | ] 44 | ]) 45 | try config.addProvider(Provider.self) 46 | 47 | do { 48 | _ = try Droplet(config) 49 | XCTFail("Should have failed.") 50 | } catch ConfigError.missingFile(let file) { 51 | XCTAssert(file == "postgresql") 52 | } catch { 53 | XCTFail("Wrong error: \(error)") 54 | } 55 | } 56 | 57 | static let allTests = [ 58 | ("testHappyPath", testHappyPath), 59 | ("testDifferentDriver", testDifferentDriver), 60 | ("testMissingConfigFails", testMissingConfigFails) 61 | ] 62 | } 63 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | ignore: 3 | - "Tests" 4 | --------------------------------------------------------------------------------