payload: {"action":"created","comment":{"url":"https://api.github.com/repos/apache/arrow/pulls/comments/360527392","pull_request_review_id":335433507,"id":360527392,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDM2MDUyNzM5Mg==","diff_hunk":"@@ -0,0 +1,935 @@\n+// Licensed to the Apache Software Foundation (ASF) under one\n+// or more contributor license agreements.  See the NOTICE file\n+// distributed with this work for additional information\n+// regarding copyright ownership.  The ASF licenses this file\n+// to you under the Apache License, Version 2.0 (the\n+// \"License\"); you may not use this file except in compliance\n+// with the License.  You may obtain a copy of the License at\n+//\n+//   http://www.apache.org/licenses/LICENSE-2.0\n+//\n+// Unless required by applicable law or agreed to in writing,\n+// software distributed under the License is distributed on an\n+// \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n+// KIND, either express or implied.  See the License for the\n+// specific language governing permissions and limitations\n+// under the License.\n+\n+#[derive(Debug, PartialEq)]\n+pub struct Field {\n+    ident: syn::Ident,\n+    ty: Type,\n+    is_a_byte_buf: bool,\n+    third_party_type: Option<ThirdPartyType>,\n+}\n+\n+/// Use third party libraries, detected\n+/// at compile time. These libraries will\n+/// be written to parquet as their preferred\n+/// physical type.\n+///\n+///   ChronoNaiveDateTime is written as i64\n+///   ChronoNaiveDate is written as i32\n+#[derive(Debug, PartialEq)]\n+enum ThirdPartyType {\n+    ChronoNaiveDateTime,\n+    ChronoNaiveDate,\n+    Uuid,\n+}\n+\n+impl Field {\n+    pub fn from(f: &syn::Field) -> Self {\n+        let ty = Type::from(f);\n+        let is_a_byte_buf = ty.physical_type() == parquet::basic::Type::BYTE_ARRAY;\n+\n+        let third_party_type = match &ty.last_part()[..] {\n+            \"NaiveDateTime\" => Some(ThirdPartyType::ChronoNaiveDateTime),\n+            \"NaiveDate\" => Some(ThirdPartyType::ChronoNaiveDate),\n+            \"Uuid\" => Some(ThirdPartyType::Uuid),\n+            _ => None,\n+        };\n+\n+        Field {\n+            ident: f\n+                .ident\n+                .clone()\n+                .expect(\"we only support structs with named fields\"),\n+            ty,\n+            is_a_byte_buf,\n+            third_party_type,\n+        }\n+    }\n+\n+    /// Takes the parsed field of the struct and emits a valid\n+    /// column writer snippet. Should match exactly what you\n+    /// would write by hand.\n+    ///\n+    /// Can only generate writers for basic structs, for example:\n+    ///\n+    /// struct Record {","path":"rust/parquet_derive/src/parquet_field.rs","position":69,"original_position":69,"commit_id":"45348e7e17f502aed6bba096e1e6569cb9aabd54","original_commit_id":"45348e7e17f502aed6bba096e1e6569cb9aabd54","user":{"login":"sunchao","id":506679,"node_id":"MDQ6VXNlcjUwNjY3OQ==","avatar_url":"https://avatars3.githubusercontent.com/u/506679?v=4","gravatar_id":"","url":"https://api.github.com/users/sunchao","html_url":"https://github.com/sunchao","followers_url":"https://api.github.com/users/sunchao/followers","following_url":"https://api.github.com/users/sunchao/following{/other_user}","gists_url":"https://api.github.com/users/sunchao/gists{/gist_id}","starred_url":"https://api.github.com/users/sunchao/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sunchao/subscriptions","organizations_url":"https://api.github.com/users/sunchao/orgs","repos_url":"https://api.github.com/users/sunchao/repos","events_url":"https://api.github.com/users/sunchao/events{/privacy}","received_events_url":"https://api.github.com/users/sunchao/received_events","type":"User","site_admin":false},"body":"nit: quotes around these?","created_at":"2019-12-20T19:03:02Z","updated_at":"2019-12-20T19:05:54Z","html_url":"https://github.com/apache/arrow/pull/4140#discussion_r360527392","pull_request_url":"https://api.github.com/repos/apache/arrow/pulls/4140","author_association":"CONTRIBUTOR","_links":{"self":{"href":"https://api.github.com/repos/apache/arrow/pulls/comments/360527392"},"html":{"href":"https://github.com/apache/arrow/pull/4140#discussion_r360527392"},"pull_request":{"href":"https://api.github.com/repos/apache/arrow/pulls/4140"}}},"pull_request":{"url":"https://api.github.com/repos/apache/arrow/pulls/4140","id":269387246,"node_id":"MDExOlB1bGxSZXF1ZXN0MjY5Mzg3MjQ2","html_url":"https://github.com/apache/arrow/pull/4140","diff_url":"https://github.com/apache/arrow/pull/4140.diff","patch_url":"https://github.com/apache/arrow/pull/4140.patch","issue_url":"https://api.github.com/repos/apache/arrow/issues/4140","number":4140,"state":"open","locked":false,"title":"ARROW-5123: [Rust] Parquet derive for simple structs","user":{"login":"xrl","id":693059,"node_id":"MDQ6VXNlcjY5MzA1OQ==","avatar_url":"https://avatars2.githubusercontent.com/u/693059?v=4","gravatar_id":"","url":"https://api.github.com/users/xrl","html_url":"https://github.com/xrl","followers_url":"https://api.github.com/users/xrl/followers","following_url":"https://api.github.com/users/xrl/following{/other_user}","gists_url":"https://api.github.com/users/xrl/gists{/gist_id}","starred_url":"https://api.github.com/users/xrl/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/xrl/subscriptions","organizations_url":"https://api.github.com/users/xrl/orgs","repos_url":"https://api.github.com/users/xrl/repos","events_url":"https://api.github.com/users/xrl/events{/privacy}","received_events_url":"https://api.github.com/users/xrl/received_events","type":"User","site_admin":false},"body":"A rebase and significant rewrite of https://github.com/sunchao/parquet-rs/pull/197\r\n\r\nBig improvement: I now use a more natural nested enum style, it helps break out what patterns of data types are . The rest of the broad strokes still apply.\r\n\r\nGoal\r\n===\r\n\r\nWriting many columns to a file is a chore. If you can put your values in to a struct which mirrors the schema of your file, this `derive(ParquetRecordWriter)` will write out all the fields, in the order in which they are defined, to a row_group.\r\n\r\nHow to Use\r\n===\r\n\r\n```\r\nextern crate parquet;\r\n#[macro_use] extern crate parquet_derive;\r\n\r\n#[derive(ParquetRecordWriter)]\r\nstruct ACompleteRecord<'a> {\r\n  pub a_bool: bool,\r\n  pub a_str: &'a str,\r\n}\r\n```\r\n\r\nRecordWriter trait\r\n===\r\n\r\nThis is the new trait which `parquet_derive` will implement for your structs.\r\n\r\n```\r\nuse super::RowGroupWriter;\r\n\r\npub trait RecordWriter<T> {\r\n  fn write_to_row_group(&self, row_group_writer: &mut Box<RowGroupWriter>);\r\n}\r\n```\r\n\r\nHow does it work?\r\n===\r\n\r\nThe `parquet_derive` crate adds code generating functionality to the rust compiler. The code generation takes rust syntax and emits additional syntax. This macro expansion works on rust 1.15+ stable. This is a dynamic plugin, loaded by the machinery in cargo. Users don't have to do any special `build.rs` steps or anything like that, it's automatic by including `parquet_derive` in their project. The `parquet_derive/src/Cargo.toml` has a section saying as much:\r\n\r\n```\r\n[lib]\r\nproc-macro = true\r\n```\r\n\r\nThe rust struct tagged with `#[derive(ParquetRecordWriter)]` is provided to the `parquet_record_writer` function in `parquet_derive/src/lib.rs`. The `syn` crate parses the struct from a string-representation to a AST (a recursive enum value). The AST contains all the values I care about when generating a `RecordWriter` impl:\r\n\r\n - the name of the struct\r\n - the lifetime variables of the struct\r\n - the fields of the struct\r\n\r\nThe fields of the struct are translated from AST to a flat `FieldInfo` struct. It has the bits I care about for writing a column: `field_name`, `field_lifetime`, `field_type`, `is_option`, `column_writer_variant`.\r\n\r\nThe code then does the equivalent of templating to build the `RecordWriter` implementation. The templating functionality is provided by the `quote` crate. At a high-level the template for `RecordWriter` looks like:\r\n\r\n```\r\nimpl RecordWriter for $struct_name {\r\n  fn write_row_group(..) {\r\n    $({\r\n      $column_writer_snippet\r\n    })\r\n  } \r\n}\r\n```\r\n\r\nthis template is then added under the struct definition, ending up something like:\r\n\r\n```\r\nstruct MyStruct {\r\n}\r\nimpl RecordWriter for MyStruct {\r\n  fn write_row_group(..) {\r\n    {\r\n       write_col_1();\r\n    };\r\n   {\r\n       write_col_2();\r\n   }\r\n  }\r\n}\r\n```\r\n\r\nand finally _THIS_ is the code passed to rustc. It's just code now, fully expanded and standalone. If a user ever changes their `struct MyValue` definition the `ParquetRecordWriter` will be regenerated. There's no intermediate values to version control or worry about.\r\n\r\nViewing the Derived Code\r\n===\r\n\r\nTo see the generated code before it's compiled, one very useful bit is to install `cargo expand` [more info on gh](https://github.com/dtolnay/cargo-expand), then you can do:\r\n\r\n```\r\n$WORK_DIR/parquet-rs/parquet_derive_test\r\ncargo expand --lib > ../temp.rs\r\n```\r\n\r\nthen you can dump the contents:\r\n\r\n```\r\nstruct DumbRecord {\r\n    pub a_bool: bool,\r\n    pub a2_bool: bool,\r\n}\r\nimpl RecordWriter<DumbRecord> for &[DumbRecord] {\r\n    fn write_to_row_group(\r\n        &self,\r\n        row_group_writer: &mut Box<parquet::file::writer::RowGroupWriter>,\r\n    ) {\r\n        let mut row_group_writer = row_group_writer;\r\n        {\r\n            let vals: Vec<bool> = self.iter().map(|x| x.a_bool).collect();\r\n            let mut column_writer = row_group_writer.next_column().unwrap().unwrap();\r\n            if let parquet::column::writer::ColumnWriter::BoolColumnWriter(ref mut typed) =\r\n                column_writer\r\n            {\r\n                typed.write_batch(&vals[..], None, None).unwrap();\r\n            }\r\n            row_group_writer.close_column(column_writer).unwrap();\r\n        };\r\n        {\r\n            let vals: Vec<bool> = self.iter().map(|x| x.a2_bool).collect();\r\n            let mut column_writer = row_group_writer.next_column().unwrap().unwrap();\r\n            if let parquet::column::writer::ColumnWriter::BoolColumnWriter(ref mut typed) =\r\n                column_writer\r\n            {\r\n                typed.write_batch(&vals[..], None, None).unwrap();\r\n            }\r\n            row_group_writer.close_column(column_writer).unwrap();\r\n        }\r\n    }\r\n}\r\n```\r\n\r\nnow I need to write out all the combinations of types we support and make sure it writes out data.\r\n\r\nProcedural Macros\r\n===\r\n\r\nThe `parquet_derive` crate can ONLY export the derivation functionality. No traits, nothing else. The derive crate can not host test cases. It's kind of like a \"dummy\" crate which is only used by the compiler, never the code.\r\n\r\nThe parent crate cannot use the derivation functionality, which is important because it means test code cannot be in the parent crate. This forces us to have a third crate, `parquet_derive_test`.\r\n\r\nI'm open to being wrong on any one of these finer points. I had to bang on this for a while to get it to compile!\r\n\r\nPotentials For Better Design\r\n===\r\n\r\n - [x] Recursion could be limited by generating the code as \"snippets\" instead of one big `quote!` AST generator. Or so I think. It might be nicer to push generating each columns writing code to another loop.\r\n - [X] ~~It would be nicer if I didn't have to be so picky about data going in to the `write_batch` function. Is it possible we could make a version of the function which accept `Into<DataType>` or similar? This would greatly simplify this derivation code as it would not need to enumerate all the supported types. Something like `write_generic_batch(&[impl Into<DataType>])` would be neat.~~ (not tackling in this generation of the plugin)\r\n - [X] ~~Another idea to improving writing columns, could we have a write function for `Iterator`s? I already have a `Vec<DumbRecord>`, if I could just write a mapping for accessing the one value, we could skip the whole intermediate vec for `write_batch`. Should have some significant memory advantages.~~ (not tackling in this generation of the plugin, it's a bigger parquet-rs enhancement)\r\n - [X] ~~It might be worthwhile to derive a parquet schema directly from a struct definition. That should stamp out opportunities for type errors.~~ (moved to #203)\r\n\r\nStatus\r\n===\r\n\r\nI have successfully integrated this work with my own data exporter (takes postgres/couchdb and outputs a single parquet file).\r\n\r\nI think this code is worth including in the project, with the caveat that it only generates simplistic `RecordWriter`s. As people start to use we can add code generation for more complex, nested structs. We can convert the nested matching style to a fancier looping style. But for now, this explicit nesting is easier to debug and understand (to me at least!).","created_at":"2019-04-11T01:03:08Z","updated_at":"2019-12-20T19:05:54Z","closed_at":null,"merged_at":null,"merge_commit_sha":"e09144a22742d4b82de3fe1e6854746449dfafb1","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[{"id":1271628855,"node_id":"MDU6TGFiZWwxMjcxNjI4ODU1","url":"https://api.github.com/repos/apache/arrow/labels/lang-rust","name":"lang-rust","color":"e8a28f","default":false,"description":"about the Rust implementation"},{"id":1273179190,"node_id":"MDU6TGFiZWwxMjczMTc5MTkw","url":"https://api.github.com/repos/apache/arrow/labels/parquet","name":"parquet","color":"e2b2f7","default":false,"description":""}],"milestone":null,"commits_url":"https://api.github.com/repos/apache/arrow/pulls/4140/commits","review_comments_url":"https://api.github.com/repos/apache/arrow/pulls/4140/comments","review_comment_url":"https://api.github.com/repos/apache/arrow/pulls/comments{/number}","comments_url":"https://api.github.com/repos/apache/arrow/issues/4140/comments","statuses_url":"https://api.github.com/repos/apache/arrow/statuses/45348e7e17f502aed6bba096e1e6569cb9aabd54","head":{"label":"tureus:parquet_derive","ref":"parquet_derive","sha":"45348e7e17f502aed6bba096e1e6569cb9aabd54","user":{"login":"tureus","id":8347289,"node_id":"MDEyOk9yZ2FuaXphdGlvbjgzNDcyODk=","avatar_url":"https://avatars1.githubusercontent.com/u/8347289?v=4","gravatar_id":"","url":"https://api.github.com/users/tureus","html_url":"https://github.com/tureus","followers_url":"https://api.github.com/users/tureus/followers","following_url":"https://api.github.com/users/tureus/following{/other_user}","gists_url":"https://api.github.com/users/tureus/gists{/gist_id}","starred_url":"https://api.github.com/users/tureus/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tureus/subscriptions","organizations_url":"https://api.github.com/users/tureus/orgs","repos_url":"https://api.github.com/users/tureus/repos","events_url":"https://api.github.com/users/tureus/events{/privacy}","received_events_url":"https://api.github.com/users/tureus/received_events","type":"Organization","site_admin":false},"repo":{"id":179600058,"node_id":"MDEwOlJlcG9zaXRvcnkxNzk2MDAwNTg=","name":"arrow","full_name":"tureus/arrow","private":false,"owner":{"login":"tureus","id":8347289,"node_id":"MDEyOk9yZ2FuaXphdGlvbjgzNDcyODk=","avatar_url":"https://avatars1.githubusercontent.com/u/8347289?v=4","gravatar_id":"","url":"https://api.github.com/users/tureus","html_url":"https://github.com/tureus","followers_url":"https://api.github.com/users/tureus/followers","following_url":"https://api.github.com/users/tureus/following{/other_user}","gists_url":"https://api.github.com/users/tureus/gists{/gist_id}","starred_url":"https://api.github.com/users/tureus/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tureus/subscriptions","organizations_url":"https://api.github.com/users/tureus/orgs","repos_url":"https://api.github.com/users/tureus/repos","events_url":"https://api.github.com/users/tureus/events{/privacy}","received_events_url":"https://api.github.com/users/tureus/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/tureus/arrow","description":"Apache Arrow is a cross-language development platform for in-memory data. It specifies a standardized language-independent columnar memory format for flat and hierarchical data, organized for efficient analytic operations on modern hardware. It also provides computational libraries and zero-copy streaming messaging and interprocess communication. Languages currently supported include C, C++, Java, JavaScript, Python, and Ruby.","fork":true,"url":"https://api.github.com/repos/tureus/arrow","forks_url":"https://api.github.com/repos/tureus/arrow/forks","keys_url":"https://api.github.com/repos/tureus/arrow/keys{/key_id}","collaborators_url":"https://api.github.com/repos/tureus/arrow/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/tureus/arrow/teams","hooks_url":"https://api.github.com/repos/tureus/arrow/hooks","issue_events_url":"https://api.github.com/repos/tureus/arrow/issues/events{/number}","events_url":"https://api.github.com/repos/tureus/arrow/events","assignees_url":"https://api.github.com/repos/tureus/arrow/assignees{/user}","branches_url":"https://api.github.com/repos/tureus/arrow/branches{/branch}","tags_url":"https://api.github.com/repos/tureus/arrow/tags","blobs_url":"https://api.github.com/repos/tureus/arrow/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/tureus/arrow/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/tureus/arrow/git/refs{/sha}","trees_url":"https://api.github.com/repos/tureus/arrow/git/trees{/sha}","statuses_url":"https://api.github.com/repos/tureus/arrow/statuses/{sha}","languages_url":"https://api.github.com/repos/tureus/arrow/languages","stargazers_url":"https://api.github.com/repos/tureus/arrow/stargazers","contributors_url":"https://api.github.com/repos/tureus/arrow/contributors","subscribers_url":"https://api.github.com/repos/tureus/arrow/subscribers","subscription_url":"https://api.github.com/repos/tureus/arrow/subscription","commits_url":"https://api.github.com/repos/tureus/arrow/commits{/sha}","git_commits_url":"https://api.github.com/repos/tureus/arrow/git/commits{/sha}","comments_url":"https://api.github.com/repos/tureus/arrow/comments{/number}","issue_comment_url":"https://api.github.com/repos/tureus/arrow/issues/comments{/number}","contents_url":"https://api.github.com/repos/tureus/arrow/contents/{+path}","compare_url":"https://api.github.com/repos/tureus/arrow/compare/{base}...{head}","merges_url":"https://api.github.com/repos/tureus/arrow/merges","archive_url":"https://api.github.com/repos/tureus/arrow/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/tureus/arrow/downloads","issues_url":"https://api.github.com/repos/tureus/arrow/issues{/number}","pulls_url":"https://api.github.com/repos/tureus/arrow/pulls{/number}","milestones_url":"https://api.github.com/repos/tureus/arrow/milestones{/number}","notifications_url":"https://api.github.com/repos/tureus/arrow/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/tureus/arrow/labels{/name}","releases_url":"https://api.github.com/repos/tureus/arrow/releases{/id}","deployments_url":"https://api.github.com/repos/tureus/arrow/deployments","created_at":"2019-04-05T01:04:36Z","updated_at":"2019-04-05T01:04:46Z","pushed_at":"2019-12-05T02:46:58Z","git_url":"git://github.com/tureus/arrow.git","ssh_url":"git@github.com:tureus/arrow.git","clone_url":"https://github.com/tureus/arrow.git","svn_url":"https://github.com/tureus/arrow","homepage":"https://arrow.apache.org","size":42435,"stargazers_count":0,"watchers_count":0,"language":"C++","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":0,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"apache:master","ref":"master","sha":"d9234628a4f510117d7d7c36e5c816e5bea1d9c4","user":{"login":"apache","id":47359,"node_id":"MDEyOk9yZ2FuaXphdGlvbjQ3MzU5","avatar_url":"https://avatars0.githubusercontent.com/u/47359?v=4","gravatar_id":"","url":"https://api.github.com/users/apache","html_url":"https://github.com/apache","followers_url":"https://api.github.com/users/apache/followers","following_url":"https://api.github.com/users/apache/following{/other_user}","gists_url":"https://api.github.com/users/apache/gists{/gist_id}","starred_url":"https://api.github.com/users/apache/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/apache/subscriptions","organizations_url":"https://api.github.com/users/apache/orgs","repos_url":"https://api.github.com/users/apache/repos","events_url":"https://api.github.com/users/apache/events{/privacy}","received_events_url":"https://api.github.com/users/apache/received_events","type":"Organization","site_admin":false},"repo":{"id":51905353,"node_id":"MDEwOlJlcG9zaXRvcnk1MTkwNTM1Mw==","name":"arrow","full_name":"apache/arrow","private":false,"owner":{"login":"apache","id":47359,"node_id":"MDEyOk9yZ2FuaXphdGlvbjQ3MzU5","avatar_url":"https://avatars0.githubusercontent.com/u/47359?v=4","gravatar_id":"","url":"https://api.github.com/users/apache","html_url":"https://github.com/apache","followers_url":"https://api.github.com/users/apache/followers","following_url":"https://api.github.com/users/apache/following{/other_user}","gists_url":"https://api.github.com/users/apache/gists{/gist_id}","starred_url":"https://api.github.com/users/apache/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/apache/subscriptions","organizations_url":"https://api.github.com/users/apache/orgs","repos_url":"https://api.github.com/users/apache/repos","events_url":"https://api.github.com/users/apache/events{/privacy}","received_events_url":"https://api.github.com/users/apache/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/apache/arrow","description":"Apache Arrow is a cross-language development platform for in-memory data. It specifies a standardized language-independent columnar memory format for flat and hierarchical data, organized for efficient analytic operations on modern hardware. It also provides computational libraries and zero-copy streaming messaging and interprocess communication. Languages currently supported include C, C++, Java, JavaScript, Python, and Ruby.","fork":false,"url":"https://api.github.com/repos/apache/arrow","forks_url":"https://api.github.com/repos/apache/arrow/forks","keys_url":"https://api.github.com/repos/apache/arrow/keys{/key_id}","collaborators_url":"https://api.github.com/repos/apache/arrow/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/apache/arrow/teams","hooks_url":"https://api.github.com/repos/apache/arrow/hooks","issue_events_url":"https://api.github.com/repos/apache/arrow/issues/events{/number}","events_url":"https://api.github.com/repos/apache/arrow/events","assignees_url":"https://api.github.com/repos/apache/arrow/assignees{/user}","branches_url":"https://api.github.com/repos/apache/arrow/branches{/branch}","tags_url":"https://api.github.com/repos/apache/arrow/tags","blobs_url":"https://api.github.com/repos/apache/arrow/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/apache/arrow/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/apache/arrow/git/refs{/sha}","trees_url":"https://api.github.com/repos/apache/arrow/git/trees{/sha}","statuses_url":"https://api.github.com/repos/apache/arrow/statuses/{sha}","languages_url":"https://api.github.com/repos/apache/arrow/languages","stargazers_url":"https://api.github.com/repos/apache/arrow/stargazers","contributors_url":"https://api.github.com/repos/apache/arrow/contributors","subscribers_url":"https://api.github.com/repos/apache/arrow/subscribers","subscription_url":"https://api.github.com/repos/apache/arrow/subscription","commits_url":"https://api.github.com/repos/apache/arrow/commits{/sha}","git_commits_url":"https://api.github.com/repos/apache/arrow/git/commits{/sha}","comments_url":"https://api.github.com/repos/apache/arrow/comments{/number}","issue_comment_url":"https://api.github.com/repos/apache/arrow/issues/comments{/number}","contents_url":"https://api.github.com/repos/apache/arrow/contents/{+path}","compare_url":"https://api.github.com/repos/apache/arrow/compare/{base}...{head}","merges_url":"https://api.github.com/repos/apache/arrow/merges","archive_url":"https://api.github.com/repos/apache/arrow/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/apache/arrow/downloads","issues_url":"https://api.github.com/repos/apache/arrow/issues{/number}","pulls_url":"https://api.github.com/repos/apache/arrow/pulls{/number}","milestones_url":"https://api.github.com/repos/apache/arrow/milestones{/number}","notifications_url":"https://api.github.com/repos/apache/arrow/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/apache/arrow/labels{/name}","releases_url":"https://api.github.com/repos/apache/arrow/releases{/id}","deployments_url":"https://api.github.com/repos/apache/arrow/deployments","created_at":"2016-02-17T08:00:23Z","updated_at":"2019-12-20T18:19:02Z","pushed_at":"2019-12-20T18:18:58Z","git_url":"git://github.com/apache/arrow.git","ssh_url":"git@github.com:apache/arrow.git","clone_url":"https://github.com/apache/arrow.git","svn_url":"https://github.com/apache/arrow","homepage":"https://arrow.apache.org","size":42138,"stargazers_count":4897,"watchers_count":4897,"language":"C++","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":1228,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":77,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":1228,"open_issues":77,"watchers":4897,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/apache/arrow/pulls/4140"},"html":{"href":"https://github.com/apache/arrow/pull/4140"},"issue":{"href":"https://api.github.com/repos/apache/arrow/issues/4140"},"comments":{"href":"https://api.github.com/repos/apache/arrow/issues/4140/comments"},"review_comments":{"href":"https://api.github.com/repos/apache/arrow/pulls/4140/comments"},"review_comment":{"href":"https://api.github.com/repos/apache/arrow/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/apache/arrow/pulls/4140/commits"},"statuses":{"href":"https://api.github.com/repos/apache/arrow/statuses/45348e7e17f502aed6bba096e1e6569cb9aabd54"}},"author_association":"NONE"}}
other: {"actor":{"display_login":"sunchao"}}