├── LICENSE ├── README.md ├── example ├── rss.go ├── yc.go └── yf.go └── yql.go /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Yasuhiro Matsumoto 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # YQL GoLang api wrapper 2 | 3 | ## Simple Yql Queries 4 | 5 | ```go 6 | db, _ := sql.Open("yql", "") 7 | 8 | stmt, err := db.Query( 9 | "select * from rss where url = ?", 10 | "http://blog.golang.org/feeds/posts/default?alt=rss") 11 | 12 | if err != nil { 13 | return 14 | } 15 | for stmt.Next() { 16 | var data map[string]interface{} 17 | stmt.Scan(&data) 18 | fmt.Printf("%v\n", data["link"]) 19 | fmt.Printf(" %v\n\n", data["title"]) 20 | } 21 | ``` 22 | 23 | ## Private Yql Queries 24 | 25 | ```go 26 | db, _ := sql.Open("yql", *conKey+"|"+*conSecret) 27 | 28 | stmt, err := db.Query( 29 | "select * from contentanalysis.analyze where url=?", 30 | "http://www.espn.com") 31 | 32 | if err != nil { 33 | return 34 | } 35 | for stmt.Next() { 36 | var data interface{} 37 | stmt.Scan(&data) 38 | fmt.Printf("%v\n", data) 39 | } 40 | ``` 41 | -------------------------------------------------------------------------------- /example/rss.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | _ "github.com/mattn/go-yql" 7 | ) 8 | 9 | func main() { 10 | db, _ := sql.Open("yql", "") 11 | 12 | stmt, err := db.Query( 13 | "select * from atom where url = ?", 14 | "http://blog.golang.org/feeds/posts/default?alt=rss") 15 | if err != nil { 16 | fmt.Println(err) 17 | return 18 | } 19 | for stmt.Next() { 20 | var data map[string]interface{} 21 | stmt.Scan(&data) 22 | fmt.Printf("%v\n", data["link"].(map[string]interface{})["href"]) 23 | fmt.Printf(" %v\n\n", data["title"]) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /example/yc.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "database/sql" 5 | "flag" 6 | "fmt" 7 | _ "github.com/mattn/go-yql" 8 | "log" 9 | ) 10 | 11 | var ( 12 | conKey *string = flag.String("key", "", "Consumer Key") 13 | conSecret *string = flag.String("secret", "", "Consumer Secret") 14 | ) 15 | 16 | func main() { 17 | flag.Parse() 18 | db, err := sql.Open("yql", *conKey+"|"+*conSecret) 19 | if err != nil { 20 | log.Fatal(err) 21 | return 22 | } 23 | for _, arg := range flag.Args() { 24 | stmt, err := db.Query("select * from contentanalysis.analyze where url=?", arg) 25 | if err != nil { 26 | log.Fatal(err) 27 | return 28 | } 29 | for stmt.Next() { 30 | var data interface{} 31 | err = stmt.Scan(&data) 32 | if err != nil { 33 | log.Fatal(err) 34 | return 35 | } 36 | fmt.Printf("%v\n", data) 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /example/yf.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // Yahoo Finance example. Shows how to pass a custom env. 4 | 5 | import ( 6 | "database/sql" 7 | "fmt" 8 | _ "github.com/mattn/go-yql" 9 | ) 10 | 11 | func main() { 12 | db, _ := sql.Open("yql", "||store://datatables.org/alltableswithkeys") 13 | 14 | stmt, err := db.Query( 15 | "select * from yahoo.finance.historicaldata where symbol = ? and startDate = ? and endDate = ?", 16 | "YHOO", 17 | "2009-09-11", 18 | "2010-03-10") 19 | if err != nil { 20 | fmt.Println(err) 21 | return 22 | } 23 | for stmt.Next() { 24 | var data map[string]interface{} 25 | stmt.Scan(&data) 26 | fmt.Printf("%v %v %v %v %v\n", data["Date"], data["Open"], data["High"], data["Low"], data["Close"]) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /yql.go: -------------------------------------------------------------------------------- 1 | package yql 2 | 3 | import ( 4 | "database/sql" 5 | "database/sql/driver" 6 | "encoding/json" 7 | "errors" 8 | "fmt" 9 | "io" 10 | "net/http" 11 | "net/url" 12 | "strings" 13 | 14 | oauth "github.com/akrennmair/goauth" 15 | ) 16 | 17 | const endpoint = "https://query.yahooapis.com/v1/public/yql" 18 | 19 | var ( 20 | yqlOauth *oauth.OAuthConsumer 21 | ) 22 | 23 | func init() { 24 | sql.Register("yql", &YQLDriver{}) 25 | } 26 | 27 | type YQLDriver struct{} 28 | 29 | func (d *YQLDriver) Open(dsn string) (driver.Conn, error) { 30 | if len(dsn) > 1 { 31 | parts := strings.Split(dsn, "|") 32 | if len(parts) == 2 { 33 | return &YQLConn{http.DefaultClient, parts[0], parts[1], ""}, nil 34 | } 35 | if len(parts) == 3 { 36 | return &YQLConn{http.DefaultClient, parts[0], parts[1], parts[2]}, nil 37 | } 38 | 39 | } 40 | if dsn != "" { 41 | return &YQLConn{c: http.DefaultClient, env: dsn}, nil 42 | } 43 | return &YQLConn{c: http.DefaultClient}, nil 44 | } 45 | 46 | type YQLConn struct { 47 | c *http.Client 48 | key string 49 | secret string 50 | env string 51 | } 52 | 53 | func (c *YQLConn) Close() error { 54 | c.c = nil 55 | return nil 56 | } 57 | 58 | func (c *YQLConn) Begin() (driver.Tx, error) { 59 | return nil, errors.New("Begin not supported") 60 | } 61 | 62 | func (c *YQLConn) Prepare(query string) (driver.Stmt, error) { 63 | return &YQLStmt{c, query}, nil 64 | } 65 | 66 | type YQLStmt struct { 67 | c *YQLConn 68 | q string 69 | } 70 | 71 | func (s *YQLStmt) Close() error { 72 | return nil 73 | } 74 | 75 | func (s *YQLStmt) NumInput() int { 76 | // TODO: strict check 77 | return strings.Count(s.q, "?") 78 | } 79 | 80 | func (s *YQLStmt) bind(args []driver.Value) error { 81 | b := s.q 82 | for _, v := range args { 83 | // TODO: strict check 84 | b = strings.Replace(b, "?", fmt.Sprintf("%q", v), 1) 85 | } 86 | s.q = b 87 | return nil 88 | } 89 | 90 | func (s *YQLStmt) Query(args []driver.Value) (driver.Rows, error) { 91 | if err := s.bind(args); err != nil { 92 | return nil, err 93 | } 94 | 95 | var res *http.Response 96 | var err error 97 | if len(s.c.key) > 1 { 98 | // secure 99 | yqlOauth := &oauth.OAuthConsumer{ 100 | Service: "yql", 101 | RequestTokenURL: "https://api.login.yahoo.com/oauth/v2/get_request_token", 102 | AccessTokenURL: "https://api.login.yahoo.com/oauth/v2/get_token", 103 | CallBackURL: "oob", 104 | ConsumerKey: s.c.key, 105 | ConsumerSecret: s.c.secret, 106 | Timeout: 5e9, 107 | } 108 | p := oauth.Params{} 109 | p.Add(&oauth.Pair{Key: "format", Value: "json"}) 110 | p.Add(&oauth.Pair{Key: "q", Value: s.q}) 111 | 112 | s, rt, err := yqlOauth.GetRequestAuthorizationURL() 113 | if err != nil { 114 | return nil, err 115 | } 116 | var pin string 117 | fmt.Printf("Open %s In your browser.\n Allow access and then enter the PIN number\n", s) 118 | fmt.Printf("PIN Number: ") 119 | fmt.Scanln(&pin) 120 | at := yqlOauth.GetAccessToken(rt.Token, pin) 121 | 122 | res, err = yqlOauth.Get(endpoint, p, at) 123 | if err != nil { 124 | return nil, err 125 | } 126 | } else { 127 | values := url.Values{} 128 | values.Add("q", s.q) 129 | values.Add("format", "json") 130 | if s.c.env != "" { 131 | values.Add("env", s.c.env) 132 | } 133 | 134 | url := endpoint + "?" + values.Encode() 135 | res, err = http.Get(url) 136 | } 137 | 138 | if err != nil { 139 | return nil, err 140 | } 141 | defer res.Body.Close() 142 | var data interface{} 143 | err = json.NewDecoder(res.Body).Decode(&data) 144 | if err != nil { 145 | return nil, errors.New(fmt.Sprintf("Invalid Json: %v", err)) 146 | } 147 | if data == nil { 148 | return nil, errors.New("Unsupported result") 149 | } 150 | var ok bool 151 | data = data.(map[string]interface{})["query"] 152 | if data == nil { 153 | return nil, errors.New("Unsupported result") 154 | } 155 | data = data.(map[string]interface{})["results"] 156 | if data == nil { 157 | return nil, errors.New("Unsupported result") 158 | } 159 | results, ok := data.(map[string]interface{}) 160 | if !ok { 161 | return nil, errors.New("Unsupported result") 162 | } 163 | var last interface{} 164 | for _, v := range results { 165 | if vv, ok := v.([]interface{}); ok { 166 | return &YQLRows{s, 0, vv}, nil 167 | } 168 | last = v 169 | } 170 | if last != nil { 171 | return &YQLRows{s, 0, []interface{}{last}}, nil 172 | } 173 | return nil, errors.New("Unsupported result") 174 | } 175 | 176 | type YQLResult struct { 177 | s *YQLStmt 178 | } 179 | 180 | func (s *YQLStmt) Exec(args []driver.Value) (driver.Result, error) { 181 | return nil, errors.New("Exec does not supported") 182 | } 183 | 184 | type YQLRows struct { 185 | s *YQLStmt 186 | n int 187 | d []interface{} 188 | } 189 | 190 | func (rc *YQLRows) Close() error { 191 | return nil 192 | } 193 | 194 | func (rc *YQLRows) Columns() []string { 195 | return []string{"results"} 196 | } 197 | 198 | func (rc *YQLRows) Next(dest []driver.Value) error { 199 | if rc.n == len(rc.d) { 200 | return io.EOF 201 | } 202 | if s, ok := rc.d[rc.n].(string); ok { 203 | dest[0] = s 204 | } else { 205 | dest[0] = rc.d[rc.n] 206 | } 207 | rc.n++ 208 | return nil 209 | } 210 | --------------------------------------------------------------------------------