From f65a1d1f69c6d3581ba77e73edddac1d6346520c Mon Sep 17 00:00:00 2001 From: Sean Fischer Date: Fri, 16 Apr 2021 19:54:43 -0400 Subject: [PATCH] Distributable sqlite database --- cmd/lambda/update-distdb/main.go | 246 +++++++++++++++++++++++++++++++ go.mod | 2 +- go.sum | 44 +----- phish_food/etl.py | 62 ++++++++ sql/bootstrap.sql | 9 -- sql/create_counts.sql | 24 +-- sql/create_symbols.sql | 15 +- 7 files changed, 330 insertions(+), 72 deletions(-) create mode 100644 cmd/lambda/update-distdb/main.go delete mode 100644 sql/bootstrap.sql diff --git a/cmd/lambda/update-distdb/main.go b/cmd/lambda/update-distdb/main.go new file mode 100644 index 0000000..cb1c516 --- /dev/null +++ b/cmd/lambda/update-distdb/main.go @@ -0,0 +1,246 @@ +package main + +import ( + "github.com/aws/aws-lambda-go/lambda" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/s3" + + db "github.com/fischersean/phish-food/internal/database" + "github.com/fischersean/phish-food/internal/etl" + _ "github.com/fischersean/phish-food/internal/tzinit" + + "database/sql" + _ "github.com/mattn/go-sqlite3" + + "bytes" + "fmt" + "io/ioutil" + "log" + "os" + "strings" + "time" +) + +const ( + DB_NAME = "kettle.db" +) + +func clearTbl(conn *sql.DB, tblName string) (err error) { + clearStmtString := fmt.Sprintf("DELETE FROM %s", tblName) + clearStmt, err := conn.Prepare(clearStmtString) + if err != nil { + return err + } + _, err = clearStmt.Exec() + return err +} + +func repopulateSymbols(conn *sql.DB, s3Service *s3.S3) (err error) { + + // Empty the symbols table + err = clearTbl(conn, "Symbols") + if err != nil { + return err + } + + tradeablesBucket := os.Getenv("TRADEABLES_BUCKET") + tickerPopulation, err := etl.GetTradeableSecurities(s3Service, tradeablesBucket) + if err != nil { + return err + } + + stmtString := "INSERT INTO Symbols (Ticker, Exchange, FullName, ETF) VALUES(?, ?, ?, ?)" + stmt, err := conn.Prepare(stmtString) + if err != nil { + return err + } + + for _, sym := range tickerPopulation { + if _, err = stmt.Exec(sym.Symbol, sym.Exchange, sym.FullName, sym.ETF); err != nil { + p := strings.Split(sym.Symbol, ":") + if p[0] == "File Creation Time" || p[0] == "" { + // reset + err = nil + continue + } + log.Println(sym.Symbol) + return err + } + } + + return err + +} + +func repopulateCounts(conn *sql.DB, sess *session.Session) (err error) { + + // Empty table + err = clearTbl(conn, "Counts") + if err != nil { + return err + } + + dynamoConn, err := db.Connect(db.ConnectionInput{ + Session: sess, + }) + if err != nil { + return err + } + + stopDate, err := time.Parse(time.RFC822, "20 Mar 21 00:00 UTC") + if err != nil { + return err + } + + for date := time.Now(); date.After(stopDate); date = date.Add(-24 * time.Hour) { + for _, sub := range etl.FetchTargets { + etlRecords, err := dynamoConn.GetEtlResultsRecord(db.EtlResultsQueryInput{ + Subreddit: sub, + Date: date, + }) + if err != nil { + return err + } + + stmtString := ` + INSERT INTO Counts ( + Subreddit, + FormatedDate, + Hour, + Ticker, + PostScore, + CommentScore, + PostMentions, + CommentMentions, + TotalScore + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)` + stmt, err := conn.Prepare(stmtString) + if err != nil { + return err + } + + for _, record := range etlRecords { + formatDate := strings.Split(record.Id, "_") + for _, data := range record.Data { + if _, err := stmt.Exec(sub, + formatDate[1], + record.Hour, + data.Stock.Symbol, + data.Count.PostScore, + data.Count.CommentScore, + data.Count.PostMentions, + data.Count.CommentMentions, + data.Count.TotalScore, + ); err != nil { + log.Printf("%s: %s", err.Error(), record.Id) + // Reset since we dont really care about fixing errors. We only want to know one happened + err = nil + } + } + } + } + } + + return err +} + +func downloadDb(svc *s3.S3, distBucketName string) (err error) { + + // Download db file from bucket + result, err := svc.GetObject(&s3.GetObjectInput{ + Bucket: aws.String(distBucketName), + Key: aws.String(DB_NAME), + }, + ) + if err != nil { + return err + } + defer result.Body.Close() + + var buf []byte + buf, err = ioutil.ReadAll(result.Body) + if err != nil { + return err + } + + err = ioutil.WriteFile(DB_NAME, buf, 0644) + if err != nil { + return err + } + + return err +} + +func uploadDb(svc *s3.S3, distBucketName string) (err error) { + + buf, err := ioutil.ReadFile(DB_NAME) + if err != nil { + return err + } + + rs := bytes.NewReader(buf) + _, err = svc.PutObject(&s3.PutObjectInput{ + Bucket: aws.String(distBucketName), + Body: rs, + Key: aws.String(DB_NAME), + }, + ) + + return err +} + +func Handler() (err error) { + + // Init session + sess := session.Must(session.NewSessionWithOptions(session.Options{ + SharedConfigState: session.SharedConfigEnable, + })) + + S3Service := s3.New(sess) + + distBucketName := os.Getenv("DIST_BUCKET") + err = downloadDb(S3Service, distBucketName) + if err != nil { + return err + } + + if os.Getenv("DEV") != "YES" { + defer os.Remove(DB_NAME) + } + + // Perform db update + conn, err := sql.Open("sqlite3", DB_NAME) + if err != nil { + log.Fatal(err.Error()) + } + + log.Println("Updating symbols table") + err = repopulateSymbols(conn, S3Service) + if err != nil { + return err + } + log.Println("Finished updating symbols table") + + log.Println("Updating counts table") + err = repopulateCounts(conn, sess) + if err != nil { + return err + } + log.Println("Finished updating counts table") + + // Upload the file back to s3 + err = uploadDb(S3Service, distBucketName) + return err +} + +func main() { + if os.Getenv("DEV") == "YES" { + err := Handler() + if err != nil { + log.Println(err.Error()) + } + return + } + lambda.Start(Handler) +} diff --git a/go.mod b/go.mod index 34a89ba..58f2cee 100644 --- a/go.mod +++ b/go.mod @@ -6,5 +6,5 @@ require ( github.com/aws/aws-lambda-go v1.22.0 github.com/aws/aws-sdk-go v1.37.14 github.com/jlaffaye/ftp v0.0.0-20201112195030-9aae4d151126 - github.com/lestrrat-go/jwx v1.1.5 + github.com/mattn/go-sqlite3 v1.14.7 ) diff --git a/go.sum b/go.sum index 7fb0128..184503e 100644 --- a/go.sum +++ b/go.sum @@ -9,32 +9,14 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8 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/decred/dcrd/chaincfg/chainhash v1.0.2/go.mod h1:BpbrGgrPTr3YJYRN3Bm+D9NuaFd+zGyNeIKgrhCXK60= -github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= -github.com/decred/dcrd/dcrec/secp256k1/v3 v3.0.0 h1:sgNeV1VRMDzs6rzyPpxyM0jp317hnwiq58Filgag2xw= -github.com/decred/dcrd/dcrec/secp256k1/v3 v3.0.0/go.mod h1:J70FGZSbzsjecRTiTzER+3f1KZLNaXkuv+yeFTKoxM8= -github.com/goccy/go-json v0.4.7 h1:xGUjaNfhpqhKAV2LoyNXihFLZ8ABSST8B+W+duHqkPI= -github.com/goccy/go-json v0.4.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/jlaffaye/ftp v0.0.0-20201112195030-9aae4d151126 h1:ly2C51IMpCCV8RpTDRXgzG/L9iZXb8ePEixaew/HwBs= github.com/jlaffaye/ftp v0.0.0-20201112195030-9aae4d151126/go.mod h1:2lmrmq866uF2tnje75wQHzmPXhmSWUt7Gyx2vgK1RCU= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -github.com/lestrrat-go/backoff/v2 v2.0.7 h1:i2SeK33aOFJlUNJZzf2IpXRBvqBBnaGXfY5Xaop/GsE= -github.com/lestrrat-go/backoff/v2 v2.0.7/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y= -github.com/lestrrat-go/codegen v1.0.0/go.mod h1:JhJw6OQAuPEfVKUCLItpaVLumDGWQznd1VaXrBk9TdM= -github.com/lestrrat-go/httpcc v1.0.0 h1:FszVC6cKfDvBKcJv646+lkh4GydQg2Z29scgUfkOpYc= -github.com/lestrrat-go/httpcc v1.0.0/go.mod h1:tGS/u00Vh5N6FHNkExqGGNId8e0Big+++0Gf8MBnAvE= -github.com/lestrrat-go/iter v1.0.0 h1:QD+hHQPDSHC4rCJkZYY/yXChYr/vjfBopKekTc+7l4Q= -github.com/lestrrat-go/iter v1.0.0/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc= -github.com/lestrrat-go/jwx v1.1.5 h1:TZ0Vly07R/duLIRS11CennIovzBvzF46ofWGj1vB26A= -github.com/lestrrat-go/jwx v1.1.5/go.mod h1:VE4Y8PnxQ1hWQ34Nbx1EbIAgs+IzsEhANW4zvkFQZW0= -github.com/lestrrat-go/option v0.0.0-20210103042652-6f1ecfceda35/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= -github.com/lestrrat-go/option v1.0.0 h1:WqAWL8kh8VcSoD6xjSH34/1m8yxluXQbDeKNfvFeEO4= -github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= -github.com/lestrrat-go/pdebug/v3 v3.0.1 h1:3G5sX/aw/TbMTtVc9U7IHBWRZtMvwvBziF1e4HoQtv8= -github.com/lestrrat-go/pdebug/v3 v3.0.1/go.mod h1:za+m+Ve24yCxTEhR59N7UlnJomWwCiIqbJRmKeiADU4= +github.com/mattn/go-sqlite3 v1.14.7 h1:fxWBnXkxfM6sRiuH3bqJ4CfzZojMOLVc0UTsTglEghA= +github.com/mattn/go-sqlite3 v1.14.7/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -42,43 +24,21 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201217014255-9d1352758620 h1:3wPMTskHO3+O6jqTEXyFcsnuxMQOqYSaHsDxcbUXpqA= -golang.org/x/crypto v0.0.0-20201217014255-9d1352758620/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200918232735-d647fc253266/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= -golang.org/x/tools v0.0.0-20210114065538-d78b04bdf963/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/phish_food/etl.py b/phish_food/etl.py index 41fb453..edc0b55 100644 --- a/phish_food/etl.py +++ b/phish_food/etl.py @@ -34,6 +34,12 @@ def __init__( Create Fargate task """ tradeables_bucket, tradeables_update_func = self.tradeables() + distdb_bucket, distdb_update_func = self.distdb( + tradeables_bucket, + count_results_table, + reddit_archive_bucket, + ) + etl_task = self.fargate_etl( vpc, cluster, @@ -84,6 +90,62 @@ def tradeables(self) -> (s3.Bucket, lambda_.Function): return bucket, handler + def distdb( + self, + tradeables_bucket: s3.Bucket, + count_results_table: dynamodb.Table, + reddit_archive_bucket: s3.Bucket, + ) -> (s3.Bucket, lambda_.Function): + + bucket = s3.Bucket( + self, + "DistributedDatabase", + removal_policy=core.RemovalPolicy.DESTROY, + versioned=True, + ) + + handler = lambda_.Function( + self, + "UpdateDistDbFunction", + runtime=lambda_.Runtime.GO_1_X, + code=lambda_.Code.from_asset( + ".", + bundling=core.BundlingOptions( + user="root", + image=lambda_.Runtime.GO_1_X.bundling_docker_image, + command=[ + "bash", + "-c", + "GOOS=linux go build -o /asset-output/main cmd/lambda/update-distdb/main.go", + ], + ), + ), + handler="main", + environment={ + "DIST_BUCKET": bucket.bucket_name, + "TRADEABLES_BUCKET": tradeables_bucket.bucket_name, + "ETL_RESULTS_TABLE": count_results_table.table_name, + "REDDIT_ARCHIVE_BUCKET": reddit_archive_bucket.bucket_name, + }, + timeout=core.Duration.minutes(5), + ) + + bucket.grant_read_write(handler) + + # Update db ever day at 11:30 + # This should ensure that all of that day's ETL results are available + rule = aws_events.Rule( + self, + "UpdateDistDbSchedule", + schedule=aws_events.Schedule.cron( + minute="30", hour="23", day="*", month="*", year="*" + ), + ) + + rule.add_target(targets.LambdaFunction(handler)) + + return bucket, handler + def fargate_etl( self, vpc: ec2.Vpc, diff --git a/sql/bootstrap.sql b/sql/bootstrap.sql deleted file mode 100644 index 8f6b7d0..0000000 --- a/sql/bootstrap.sql +++ /dev/null @@ -1,9 +0,0 @@ --- Create the main datbase -CREATE DATABASE IF NOT EXISTS kettle; -USE kettle; - - - --- Create insert query - --- Create update symbols query diff --git a/sql/create_counts.sql b/sql/create_counts.sql index 0cfc41d..43bc1aa 100644 --- a/sql/create_counts.sql +++ b/sql/create_counts.sql @@ -1,13 +1,13 @@ -- Create count table -CREATE TABLE IF NOT EXISTS Counts ( - Id varchar(25), - CountDate datetime, - Ticker varchar(10), - PostScore int, - CommentScore int, - TotalScore float, - PostMentions int, - CommentMentions int, - PRIMARY KEY(Id) -); - +CREATE TABLE "Counts" ( + "Id" INTEGER PRIMARY KEY AUTOINCREMENT, + "Subreddit" TEXT NOT NULL, + "FormatedDate" TEXT NOT NULL, + "Hour" INTEGER NOT NULL, + "Ticker" TEXT NOT NULL, + "PostScore" INTEGER, + "CommentScore" INTEGER, + "TotalScore" REAL, + "PostMentions" INTEGER, + "CommentMentions" INTEGER +) diff --git a/sql/create_symbols.sql b/sql/create_symbols.sql index e37dbeb..118e19f 100644 --- a/sql/create_symbols.sql +++ b/sql/create_symbols.sql @@ -1,9 +1,8 @@ -- Create ticker symbol table -CREATE TABLE IF NOT EXISTS Symbols ( - Ticker varchar(10), - Exchange varchar(255), - FullName varchar(255), - ETF boolean, - PRIMARY KEY(Ticker) -); - +CREATE TABLE "Symbols" ( + "Ticker" TEXT UNIQUE, + "Exchange" TEXT, + "FullName" TEXT, + "ETF" boolean, + PRIMARY KEY("Ticker") +)