From ba770f5fe358eb2d49d80933942b1e1560a825df Mon Sep 17 00:00:00 2001 From: Sergey Avseyev Date: Thu, 18 Apr 2024 00:07:23 -0700 Subject: [PATCH] PCBC-987: Fix consistency vector encoding for FTS --- Couchbase/MutationState.php | 4 +- Couchbase/SearchOptions.php | 15 ++---- src/wrapper/conversion_utilities.cxx | 8 ++-- src/wrapper/conversion_utilities.hxx | 41 ++++++++++++++++ tests/KeyValueScanTest.php | 1 + tests/SearchTest.php | 70 +++++++++++++++++++--------- tests/beer-data.json | 41 ++++++++++++++++ 7 files changed, 141 insertions(+), 39 deletions(-) create mode 100644 tests/beer-data.json diff --git a/Couchbase/MutationState.php b/Couchbase/MutationState.php index ab6daad9..8f8f7152 100644 --- a/Couchbase/MutationState.php +++ b/Couchbase/MutationState.php @@ -60,8 +60,8 @@ public function export(): array foreach ($this->tokens as $token) { $state[] = [ "partitionId" => $token->partitionId(), - "partitionUuid" => hexdec($token->partitionUuid()), - "sequenceNumber" => hexdec($token->sequenceNumber()), + "partitionUuid" => $token->partitionUuid(), + "sequenceNumber" => $token->sequenceNumber(), "bucketName" => $token->bucketName(), ]; } diff --git a/Couchbase/SearchOptions.php b/Couchbase/SearchOptions.php index 37378744..dbaebc4a 100644 --- a/Couchbase/SearchOptions.php +++ b/Couchbase/SearchOptions.php @@ -29,7 +29,7 @@ class SearchOptions implements JsonSerializable private ?int $skip = null; private ?bool $explain = null; private ?bool $disableScoring = null; - private ?array $consistentWith = null; + private ?MutationState $consistentWith = null; private ?array $fields = null; private ?array $facets = null; private ?array $sort = null; @@ -132,16 +132,7 @@ public function disableScoring(bool $disabled): SearchOptions */ public function consistentWith(string $index, MutationState $state): SearchOptions { - $vectors = []; - foreach ($state->tokens() as $token) { - $vectors[] = [ - 'partitionId' => $token->partitionId(), - 'partitionUuid' => $token->partitionUuid(), - 'sequenceNumber' => $token->sequenceNumber(), - 'bucketName' => $token->bucketName(), - ]; - } - $this->consistentWith = $vectors; + $this->consistentWith = $state; return $this; } @@ -322,7 +313,7 @@ public static function export(?SearchOptions $options): array 'disableScoring' => $options->disableScoring, 'fields' => $options->fields, 'sortSpecs' => $sort, - 'consistentWith' => $options->consistentWith, + 'consistentWith' => $options->consistentWith == null ? null : $options->consistentWith->export(), 'facets' => $options->facets, 'highlightStyle' => $highlightStyle, 'highlightFields' => $highlightFields, diff --git a/src/wrapper/conversion_utilities.cxx b/src/wrapper/conversion_utilities.cxx index 6262221d..1379d2b0 100644 --- a/src/wrapper/conversion_utilities.cxx +++ b/src/wrapper/conversion_utilities.cxx @@ -732,16 +732,16 @@ zval_to_common_search_request(const zend_string* index_name, const zend_string* std::uint64_t sequence_number; std::uint16_t partition_id; std::string bucket_name; - if (auto e = cb_assign_integer(partition_id, options, "partitionId"); e.ec) { + if (auto e = cb_assign_integer(partition_id, item, "partitionId"); e.ec) { return { {}, e }; } - if (auto e = cb_assign_integer(partition_uuid, options, "partitionUuid"); e.ec) { + if (auto e = cb_assign_integer(partition_uuid, item, "partitionUuid"); e.ec) { return { {}, e }; } - if (auto e = cb_assign_integer(sequence_number, options, "sequenceNumber"); e.ec) { + if (auto e = cb_assign_integer(sequence_number, item, "sequenceNumber"); e.ec) { return { {}, e }; } - if (auto e = cb_assign_string(bucket_name, options, "bucketName"); e.ec) { + if (auto e = cb_assign_string(bucket_name, item, "bucketName"); e.ec) { return { {}, e }; } vectors.emplace_back(mutation_token{ partition_uuid, sequence_number, partition_id, bucket_name }); diff --git a/src/wrapper/conversion_utilities.hxx b/src/wrapper/conversion_utilities.hxx index 2f91bf6c..0d876700 100644 --- a/src/wrapper/conversion_utilities.hxx +++ b/src/wrapper/conversion_utilities.hxx @@ -32,6 +32,7 @@ #include #include +#include namespace couchbase::transactions { @@ -67,6 +68,44 @@ query_response_to_zval(zval* return_value, const core::operations::query_respons void search_query_response_to_zval(zval* return_value, const core::operations::search_response& resp); +template +static Integer +parse_integer(const std::string& str, std::size_t* pos = 0, int base = 10) +{ + if constexpr (std::is_signed_v) { + return std::stoll(str, pos, base); + } else { + return std::stoull(str, pos, base); + } +} + +template +static std::pair> +cb_get_integer_from_hex(const zend_string* value, std::string_view name) +{ + auto hex_string = cb_string_new(value); + + if(hex_string.empty()) { + return { { errc::common::invalid_argument, ERROR_LOCATION, fmt::format("unexpected empty string for {}", name) }, {} }; + } + + try { + std::size_t pos; + auto result = parse_integer(hex_string, &pos, 16); + if (result < std::numeric_limits::min() || result > std::numeric_limits::max()) { + return { { errc::common::invalid_argument, ERROR_LOCATION, fmt::format("number out of range for {}", name) }, {} }; + } + if (pos != hex_string.length()) { + return { { errc::common::invalid_argument, ERROR_LOCATION, fmt::format("trailing garbage in {}", name) }, {} }; + } + return {{}, result}; + } catch (const std::invalid_argument& e) { + return { { errc::common::invalid_argument, ERROR_LOCATION, fmt::format("invalid hex number for {}", name) }, {} }; + } catch (const std::out_of_range& e) { + return { { errc::common::invalid_argument, ERROR_LOCATION, fmt::format("number out of range for {}", name) }, {} }; + } +} + template static std::pair> cb_get_integer(const zval* options, std::string_view name) @@ -87,6 +126,8 @@ cb_get_integer(const zval* options, std::string_view name) return {}; case IS_LONG: break; + case IS_STRING: + return cb_get_integer_from_hex(Z_STR_P(value), name); default: return { { errc::common::invalid_argument, ERROR_LOCATION, fmt::format("expected {} to be a integer value in the options", name) }, diff --git a/tests/KeyValueScanTest.php b/tests/KeyValueScanTest.php index 70543d10..d9351231 100644 --- a/tests/KeyValueScanTest.php +++ b/tests/KeyValueScanTest.php @@ -43,6 +43,7 @@ public function setUp(): void { parent::setUp(); $this->skipIfProtostellar(); + $this->skipIfUnsupported($this->version()->supportsCollections()); $this->collection = $this->defaultCollection(); for ($i = 0; $i < 100; $i++) { diff --git a/tests/SearchTest.php b/tests/SearchTest.php index 1b5f8294..5c655e83 100644 --- a/tests/SearchTest.php +++ b/tests/SearchTest.php @@ -20,11 +20,13 @@ use Couchbase\BooleanSearchQuery; use Couchbase\ClusterInterface; +use Couchbase\CollectionInterface; use Couchbase\ConjunctionSearchQuery; use Couchbase\DateRangeSearchFacet; use Couchbase\DateRangeSearchQuery; use Couchbase\DisjunctionSearchQuery; use Couchbase\DocIdSearchQuery; +use Couchbase\DurabilityLevel; use Couchbase\Exception\FeatureNotAvailableException; use Couchbase\Exception\IndexNotFoundException; use Couchbase\GeoBoundingBoxSearchQuery; @@ -52,6 +54,7 @@ use Couchbase\TermRangeSearchQuery; use Couchbase\TermSearchFacet; use Couchbase\TermSearchQuery; +use Couchbase\UpsertOptions; use Couchbase\VectorQuery; use Couchbase\VectorQueryCombination; use Couchbase\VectorSearch; @@ -63,34 +66,59 @@ class SearchTest extends Helpers\CouchbaseTestCase { private ClusterInterface $cluster; + private CollectionInterface $collection; private SearchIndexManager $indexManager; + /** + * @return number of the documents in dataset + */ + public function loadDataset(): int + { + $dataset = json_decode(file_get_contents(__DIR__ . "/beer-data.json"), true); + + $options = UpsertOptions::build()->durabilityLevel(DurabilityLevel::MAJORITY_AND_PERSIST_TO_ACTIVE); + foreach ($dataset as $id => $document) { + $this->collection->upsert($id, $document, $options); + } + + return count($dataset); + } + + public function createSearchIndex(int $datasetSize): void + { + fprintf(STDERR, "Create 'beer-search' to index %d docs\n", $datasetSize); + $indexDump = json_decode(file_get_contents(__DIR__ . "/beer-search.json"), true); + $index = SearchIndex::build("beer-search", self::env()->bucketName()); + $index->setParams($indexDump["params"]); + $this->indexManager->upsertIndex($index); + + $start = time(); + while (true) { + try { + $indexedDocuments = $this->indexManager->getIndexedDocumentsCount("beer-search"); + fprintf(STDERR, "%ds, Indexing 'beer-search': %d docs\n", time() - $start, $indexedDocuments); + if ($indexedDocuments >= $datasetSize) { + break; + } + sleep(5); + } catch (\Couchbase\Exception\IndexNotReadyException $ex) { + } + } + } + public function setUp(): void { parent::setUp(); $this->cluster = $this->connectCluster(); + $this->collection = $this->openBucket(self::env()->bucketName())->defaultCollection(); if (self::env()->useCouchbase()) { $this->indexManager = $this->cluster->searchIndexes(); try { $this->indexManager->getIndex("beer-search"); } catch (IndexNotFoundException $ex) { - $indexDump = json_decode(file_get_contents(__DIR__ . "/beer-search.json"), true); - $index = SearchIndex::build("beer-search", "beer-sample"); - $index->setParams($indexDump["params"]); - $this->indexManager->upsertIndex($index); - } - while (true) { - try { - $indexedDocuments = $this->indexManager->getIndexedDocumentsCount("beer-search"); - fprintf(STDERR, "Indexing 'beer-search': %d docs\n", $indexedDocuments); - if ($indexedDocuments > 7000) { - break; - } - sleep(3); - } catch (\Couchbase\Exception\IndexNotReadyException $ex) { - } + $this->createSearchIndex($this->loadDataset()); } } } @@ -159,6 +187,7 @@ public function testSearchWithNoHits() $this->assertEquals(0, $result->metaData()->totalHits()); } + public function testSearchWithConsistency() { $this->skipIfCaves(); @@ -173,8 +202,7 @@ public function testSearchWithConsistency() $this->assertEmpty($result->rows()); $this->assertEquals(0, $result->metaData()->totalHits()); - $collection = $this->cluster->bucket('beer-sample')->defaultCollection(); - $result = $collection->upsert($id, ["type" => "beer", "name" => $id]); + $result = $this->collection->upsert($id, ["type" => "beer", "name" => $id]); $mutationState = new MutationState(); $mutationState->add($result); @@ -358,7 +386,7 @@ public function testCompoundSearchQueries() $disjunctionQuery = new DisjunctionSearchQuery([$nameQuery, $descriptionQuery]); $options = SearchOptions::build()->fields(["type", "name", "description"]); $result = $this->cluster->searchQuery("beer-search", $disjunctionQuery, $options); - $this->assertGreaterThan(1000, $result->metaData()->totalHits()); + $this->assertGreaterThan(20, $result->metaData()->totalHits()); $this->assertNotEmpty($result->rows()); $this->assertMatchesRegularExpression('/green/i', $result->rows()[0]['fields']['name']); $this->assertDoesNotMatchRegularExpression('/hop/i', $result->rows()[0]['fields']['name']); @@ -434,18 +462,18 @@ public function testSearchWithFacets() $this->assertNotNull($result->facets()['foo']); $this->assertEquals('name', $result->facets()['foo']->field()); $this->assertEquals('ale', $result->facets()['foo']->terms()[0]->term()); - $this->assertGreaterThan(1000, $result->facets()['foo']->terms()[0]->count()); + $this->assertGreaterThan(10, $result->facets()['foo']->terms()[0]->count()); $this->assertNotNull($result->facets()['bar']); $this->assertEquals('updated', $result->facets()['bar']->field()); $this->assertEquals('old', $result->facets()['bar']->dateRanges()[0]->name()); - $this->assertGreaterThan(5000, $result->facets()['bar']->dateRanges()[0]->count()); + $this->assertGreaterThan(30, $result->facets()['bar']->dateRanges()[0]->count()); $this->assertNotNull($result->facets()['baz']); $this->assertEquals('abv', $result->facets()['baz']->field()); $this->assertEquals('light', $result->facets()['baz']->numericRanges()[0]->name()); $this->assertGreaterThan(0, $result->facets()['baz']->numericRanges()[0]->max()); - $this->assertGreaterThan(100, $result->facets()['baz']->numericRanges()[0]->count()); + $this->assertGreaterThan(15, $result->facets()['baz']->numericRanges()[0]->count()); } public function testNullInNumericRangeFacet() diff --git a/tests/beer-data.json b/tests/beer-data.json new file mode 100644 index 00000000..45e7f3cc --- /dev/null +++ b/tests/beer-data.json @@ -0,0 +1,41 @@ +{ + "anheuser_busch-green_valley_wild_hop_lager": {"name":"Green Valley Wild Hop Lager","abv":0.0,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"anheuser_busch","updated":"2010-07-22 20:00:20","description":"","style":"American-Style Lager","category":"North American Lager"}, + "aspen_brewing_company-conundrum_red_ale": {"name":"Conundrum Red Ale","abv":7.0,"ibu":65.0,"srm":13.0,"upc":0,"type":"beer","brewery_id":"aspen_brewing_company","updated":"2010-11-08 08:45:46","description":"Have you ever heard of an ESB? In most cases, it stands for Extra Special Bitter, and man is this red extra special. Conundrum walks the line between a malt-centric sweet ale and a hop-centric bitter ale so well, it","style":"American-Style Amber/Red Ale","category":"North American Ale"}, + "atwater_block_brewing-alt": {"name":"Alt","abv":0.0,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"atwater_block_brewing","updated":"2010-07-22 20:00:20","description":"","style":"American-Style Brown Ale","category":"North American Ale"}, + "big_sky_brewing-powder_hound_winter_ale": {"name":"Powder Hound Winter Ale","abv":0.0,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"big_sky_brewing","updated":"2010-07-22 20:00:20","description":"Two words describe Montana winters... Cold, dark, and snowy. And we wouldn't have it any other way, because it's the only time of year for Powder Hound, our Northern Rockies Winter Ale. Rich malt taste and an avalanche of hops will make you want to say these three words- \"I'll have another Powder Hound!\" \r\n\r\nSince Powder Hound is our own creation, it does not fit neatly into a style. Some folks would call it an \"Old Ale\" and others might call it a \"Winter Warmer\". Regardless of what you call it, Powder Hound satisfies, with the fine, hand-selected British hops fully complementing the smooth malt notes. After a day on the snow, enjoy a Powder Hound Winter Ale. Available November through March.","style":"Old Ale","category":"British Ale"}, + "boston_beer_company-samuel_adams_hallertau_imperial_pilsner": {"name":"Samuel Adams Hallertau Imperial Pilsner","abv":8.8,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"boston_beer_company","updated":"2010-07-22 20:00:20","description":"An amazing treat for hops lovers.\r\n\r\nSamuel Adams Hallertau Imperial Pilsner is a celebration of the extraordinary Hallertau Mittelfrueh hop variety. This rare Noble Bavarian hop, considered to be one of the best in the world, is prized for its quality and aromatic characteristics.\r\n\r\nThe beer, which is a deep golden color with a rich, creamy head, gives off an intense and complex Noble hop aroma unlike any other brew. With the first sip, beer enthusiasts will experience an explosion of hop flavor. The intensity of deep citrus, spicy Noble hop flavor is balanced with the slight sweetness from the malt. Due to the quality of the hops, this beer remains balanced and smoothly drinkable from beginning to end. The lingering \"hop signature\" is an amazing treat for hops lovers."}, + "brewpub_on_the_green": {"name":"Brewpub-on-the-Green","city":"Fremont","state":"California","code":"","country":"United States","phone":"","website":"","type":"brewery","updated":"2010-07-22 20:00:20","description":"","address":[],"geo":{"accuracy":"APPROXIMATE","lat":37.5483,"lon":-121.989}}, + "bulldog_brewing-aztec_amaranth_ale": {"name":"Aztec Amaranth Ale","abv":0.0,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"bulldog_brewing","updated":"2010-07-22 20:00:20","description":""}, + "coopers_brewery-coopers_premium_light": {"name":"Coopers Premium Light","abv":2.9,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"coopers_brewery","updated":"2010-07-22 20:00:20","description":"Coopers Premium Light is brewed using malted barley and no sugar. Coopers has used traditional lager brewing techniques to produce a full-flavoured light beer. The light has a fresh aroma with clean floral hop notes, excellent head and colour presentation.","style":"American-Style Light Lager","category":"North American Lager"}, + "deschutes_brewery-green_lakes_organic_ale": {"name":"Green Lakes Organic Ale","abv":5.2,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"deschutes_brewery","updated":"2010-07-22 20:00:20","description":"Discover Deschutes Brewery’s intriguing amber ale, our Ode to Mother Earth. Experience its mellow malt profile intertwined with subtly surprising hop flavors. Green Lakes Organic Ale is brewed with five types of 100% organic malted barley and balanced with Liberty and Salmon-Safe Sterling hops. Easy to drink. Easy on the Environment. Downright Delicious. Who knew celebrating Mother Earth could taste so good.\r\n\r\nAfter working with Oregon Tilth for nearly six months, Deschutes Brewery received organic certification for its 50 barrel brew house and can now brew tasty organic ales for year-round enjoyment.\r\n\r\nFish need cool clean water. So do you. That’s why we sourced Salmon-Safe certified Sterling hops for our first organic beer. The way these flavorful, rich hops are grown makes sure that streams are shaded and there is not runoff to nearby waterways. That way the rivers stay cool and clean for migrating salmon. Not only is our Green Lakes beer organic, it helps protect our rivers as well.","style":"American-Style Amber/Red Ale","category":"North American Ale"}, + "deschutes_brewery-hop_henge_imperial_ipa": {"name":"Hop Henge Imperial IPA","abv":8.75,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"deschutes_brewery","updated":"2010-07-22 20:00:20","description":"HOP HENGE IMPERIAL IPA – The Ultimate in Hop Innovation \r\n\r\nHop Henge Imperial IPA returns to the Bond Street Series line-up this April in extreme fashion. Staying true to the experimental nature of the series and the “never settle” philosophy of Deschutes, our brewers went back to the drawing board to create an amplified version of last year’s monument to hops.\r\n\r\nHead Brewer Brett Porter says, “This is a truly exciting and groundbreaking beer. We reformulated everything about the hop recipe to give Hop Henge an extraordinary aroma and flavor similar to a fresh hop beer.” In addition to the Cascade and Centennial hops, Deschutes Brewery is experimenting with a hop variety so new that it has yet to be named.\r\n\r\nThe team here at Bend Public House recommends Hop Henge as the perfect accompaniment for a variety of spicy foods, so be sure to have a bottle handy next time you make a batch of hot wings and go for the five alarm award. The high-octane hoppiness is a wildly refreshing antidote to a wide array of hot foods.\r\n\r\nLimited Availability April through June.\r\n\r\nDon’t miss this amazing hop experiment that is sure to leave your taste buds begging for more.\r\n\r\n-http://www.deschutesbrewery.com/Brews/Bond+Street+Series/default.aspx\r\n\r\nAvailable April through June.\r\n\r\nHop Henge I.P.A. was first brewed in 2006 as an agressive West Coast style I.P.A. In 2007, Hop Henge is going Imperial! Several pounds of Centennial, Cascade and Northern Brewer hops are in each barrel with a heavy dry-hop presence to top it off. A blend of crystal, pale and caraston malts creates an overall biscuity characteristic that is dense and muscular, building the alcohol base to support the monstrous hop profile.\r\n\r\nLike the rest of the Bond Street Series, Hop Henge highlights the creativity and curiosity of our brewers. Bolder than its English ancestors, with huge hops and a bitter finish, this IPA is no wallflower.\r\n\r\nAlcohol By Volume: 8.1%\r\n\t\r\n\r\nIBU 95\r\n\r\nWhen one of our brewers suggested we name our new IPA Hop Henge, he also came up with the idea of actually recreating Stonehenge, only with hop bales. We were up for the challenge and even though the weather did not want to cooperate, we pulled it off and threw a party afterwards.\r\n\t\r\n\r\n\r\nphoto: chris mather\r\nRatings, Awards & Notables\r\n\r\nWorld's Best Strong Pale Ale (Imperial IPA)\r\n2007 World Beer Awards\r\n\r\nWorld's 50 Best Beers\r\n2006 International Beer Challenge\r\n\r\nGold Medal, 92 Points\r\n2006 World Beer Championships\r\n\r\nSilver Medal, India Pale Ale Category\r\n2006 Australian International Beer Awards\r\n\r\nModern Brewery Age, February 12, 2007\r\n5 out 5 stars \r\nHop Henge started its life as an India Pale Ale, but this year it was bumped up to “Imperial IPA” status, with a hefty dose of additional hops.\r\n“This one is lovely,” said taster Tom Conti. “They got it just right.”\r\nIt pours out a deep amber, with an appealing rocky head, and rich hop aroma wafting from the glass. “They sure dosed it with a lot of hops...[there’s] a lot of hop bitterness in the taste,” one taster observed.\r\nIn addition to the Imperial-level hopping, Hop Henge also boasts Imperial-level alcohol content, with 8.1% a.b.v.\r\nThis was the top-rated beer during its tasting session, and tasters had to dig deep for new superlatives to describe it. “This is a beautiful beer,” concluded taster Gregg Glaser. “Full of flavor and hops and malt and hops again.”\r\n“Not for the timid,” said taster Robert Lachman.\r\n\r\n-http://www.deschutesbrewery.com/BrewPub/OnTap/125352.aspx","style":"Imperial or Double India Pale Ale","category":"North American Ale"}, + "dux_de_lux-ginger_tom": {"name":"Ginger Tom","abv":4.0,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"dux_de_lux","updated":"2010-07-22 20:00:20","description":"An old fashioned style ginger beer brewed naturally with honey, malt, lemon and fresh root ginger, leaving you with a refreshing crisp zesty flavour.","style":"Herb and Spice Beer","category":"Other Style"}, + "four_peaks_brewing-hop_knot_ipa": {"name":"Hop Knot IPA","abv":6.0,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"four_peaks_brewing","updated":"2010-07-22 20:00:20","description":"Our Hop Knot IPA is made only from American malt and lots of American hops, which produce a big, broad-shouldered, hoppy beer backed up by a character as warm as, well, Mom and apple pie… \r\n\r\nHop Knot IPA get its peculiar name from the weaving of four different hops added at four separate times during the brewing process. Including our cavernous hop-back, which gets so stuffed with whole leaf hops that we feel genuine guilt for its excess. Hop Knot is an ale that is to be enjoyed with friends, spicy food or any time you need a good hop fix without the harsh bitterness. We hope you enjoy this pioneering beer made in the bold spirit of Americans everywhere. \r\n\r\nAlcohol content approximately 6.0% by volume (ALWAYS ON TAP!!)","style":"American-Style India Pale Ale","category":"North American Ale"}, + "fratellos_restaurant_and_brewery-fox_river_golden_ale": {"name":"Fox River Golden Ale","abv":0.0,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"fratellos_restaurant_and_brewery","updated":"2010-07-22 20:00:20","description":""}, + "great_divide_brewing-fresh_hop_pale_ale": {"name":"Fresh Hop Pale Ale","abv":6.1,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"great_divide_brewing","updated":"2010-07-22 20:00:20","description":"The September hop harvest is a once-a-year opportunity to brew with fresh hops, also called “wet hops.” Given the perishable nature of just-harvested hop cones, they are shipped overnight to Great Divide shortly after harvest. The morning of the scheduled hop delivery in Denver, Great Divide’s brewers begin brewing Fresh Hop and are ready to hop the beer just as the fresh hops are delivered.\r\n\r\nUsing fresh hops is a big endeavor, requiring four to five times the volume of hops compared to the usual process of using pelletized hops. This complex process brings impressive results: Fresh Hop is an American-Style Pale Ale with moderate hop bitterness marked by a unique and intensely grassy hop flavor and aroma. Fresh Hop is a superbly refreshing, medium bodied, light-copper colored pale ale.","style":"American-Style Pale Ale","category":"North American Ale"}, + "green_bay_brewing": {"name":"Green Bay Brewing","city":"Green Bay","state":"Wisconsin","code":"54303","country":"United States","phone":"1-888-604-2337","website":"","type":"brewery","updated":"2010-07-22 20:00:20","description":"","address":["313 Dousman Street"],"geo":{"accuracy":"ROOFTOP","lat":44.5189,"lon":-88.0196}}, + "green_flash_brewing": {"name":"Green Flash Brewing","city":"Vista","state":"California","code":"92083","country":"United States","phone":"1-760-597-9012","website":"http://www.greenflashbrew.com","type":"brewery","updated":"2010-07-22 20:00:20","description":"","address":["1430 Vantage Court #104A"],"geo":{"accuracy":"ROOFTOP","lat":33.136,"lon":-117.225}}, + "green_mill_brewing_saint_paul": {"name":"Green Mill Brewing - Saint Paul","city":"Saint Paul","state":"Minnesota","code":"55105","country":"United States","phone":"1-651-698-0353","website":"","type":"brewery","updated":"2010-07-22 20:00:20","description":"","address":["57 Hamline Avenue South"],"geo":{"accuracy":"RANGE_INTERPOLATED","lat":44.9398,"lon":-93.1568}}, + "green_mountain_beverage": {"name":"Green Mountain Beverage","city":"Middlebury","state":"Vermont","code":"5753","country":"United States","phone":"1-802-873-9045","website":"http://www.gmbeverage.com/","type":"brewery","updated":"2010-07-22 20:00:20","description":"Green Mountain Beverage is nestled in a valley near the historic Green Mountains in Middlebury, Vermont. This is where we operate our Green Mountain Cidery (our own word we created from a derivative of brewery) and we have become the number one producer of premium alcoholic draft ciders in the U.S. Throughout these pristine areas of the Green Mountains, people are committed to preserving nature's beauty and still today carry on the long standing tradition of taking pride in their work and the products they create which are truly special. Here at Green Mountain Beverage, we also carry on these traditions. Our ciders are a unique combination of nature's best ingredients, our skills, commitment and pride in our work.","address":["153 Pond Lane"],"geo":{"accuracy":"ROOFTOP","lat":44.0344,"lon":-73.1735}}, + "harpoon_brewery_boston-glacier_harvest_09_wet_hop_100_barrel_series_28": {"name":"Glacier Harvest '09 Wet Hop (100 Barrel Series #28)","abv":6.7,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"harpoon_brewery_boston","updated":"2010-07-22 20:00:20","description":"For the 28th session of the Harpoon 100 Barrel Series, we’re celebrating this year’s hop harvest with Glacier Harvest Wet Hop beer, a pale ale made with fresh Glacier hops.\r\n \r\nWet hop beers are brewed using fresh, “wet” hops instead of traditional dried hops—hops contain about 60% moisture when they are first picked.\r\n\r\nTypically, when hops are picked they are quickly dried and refrigerated to increase shelf life and make them more consistent for brewing. Freshly picked wet hops, however, need to be used within hours of harvest or they will begin to degrade rapidly. Wet hops retain more of their natural aroma and volatile flavors that dissipate when dried. This gives wet hop beers a fresher hop flavor and aroma than that of beers hopped with processed hops.\r\nThis yields an immersed, intense hop flavor in the beer.\r\n\r\nHarpoon brewer Ray Dobens, creator of the beer, added a heroic dose of fresh hops the day of the harvest.The hop flavor and aroma from this copper-colored ale comes from a generous late addition of freshly harvested “wet” hops.","style":"American-Style Pale Ale","category":"North American Ale"}, + "left_coast_brewing-hop_juice_double_ipa": {"name":"Hop Juice Double IPA","abv":9.4,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"left_coast_brewing","updated":"2010-07-22 20:00:20","description":"Here at Left Coast Brewing Co. we pride ourselves on being one of the first breweries to pioneer a Double IPA style beer. In 2003, we brewed our first Double IPA, and haven't looked back since. This hop monster uses Premium American 2- Row and a touch of light crystal malt to create a solid malt foundation. The recipe calls for hops to be used in every step of the brewing process; in the mash, in a hop back, in the fermenter, and in the bright tanks. We use hop extract, hop pellets and hop flowers, hence the name Hop Juice. Hop Juice spends more than 4 weeks dry hopping in the fermenter and the bright beer tank. It is approximately 9.4% abv and has massive IBUs. Hop usage is over 4lbs per barrel. Hopeheads, step up to the plate!","style":"Imperial or Double India Pale Ale","category":"North American Ale"}, + "lift_bridge_brewery-harvestor_fresh_hop_ale": {"name":"Harvestör Fresh Hop Ale","abv":7.2,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"lift_bridge_brewery","updated":"2010-07-22 20:00:20","description":"Constant addition of fresh Cascade and Willamette hops from Brad’s Stillwater hop garden during boil and fermentation process. Aromatic and caramel malts compliment the hop character for an incredibly balanced beer that celebrates the hop harvest without killing your tongue.","style":"American-Style Pale Ale","category":"North American Ale"}, + "martini_brauerei-weissbier_dunkel": {"name":"Weissbier Dunkel","abv":0.0,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"martini_brauerei","updated":"2010-07-22 20:00:20","description":""}, + "mcmenamins_mill_creek-irish_stout": {"name":"Irish Stout","abv":0.0,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"mcmenamins_mill_creek","updated":"2010-07-22 20:00:20","description":"","style":"American-Style Stout","category":"North American Ale"}, + "miller_brewing-miller_genuine_draft_64": {"name":"Miller Genuine Draft 64","abv":2.8,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"miller_brewing","updated":"2010-07-22 20:00:20","description":"","style":"American-Style Light Lager","category":"North American Lager"}, + "new_jersey_beer_company-hudson_pale_ale": {"name":"Hudson Pale Ale","abv":0.0,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"new_jersey_beer_company","updated":"2010-11-17 14:13:17","description":"","style":"American-Style Pale Ale","category":"North American Ale"}, + "nicolet_brewing-blonde": {"name":"Blonde","abv":3.0,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"nicolet_brewing","updated":"2010-07-22 20:00:20","description":""}, + "odell_brewing-5_barrel_pale_ale": {"name":"5 Barrel Pale Ale","abv":5.3,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"odell_brewing","updated":"2010-07-22 20:00:20","description":"The distinctive hop character of our 5 Barrel Pale Ale is due to the extraction of essential oils from select hops. We treat 5 Barrel Pale Ale to an infusion of fresh whole hop flowers in the Hop Back and the Fermentor, as well as four hop additions during the kettle boil. We like how this gives the beer a fresh, lively flavor and aroma.","style":"Classic English-Style Pale Ale","category":"British Ale"}, + "qingdao_brewery-green_beer": {"name":"Green Beer","abv":0.0,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"qingdao_brewery","updated":"2010-07-22 20:00:20","description":"","style":"American-Style Lager","category":"North American Lager"}, + "river_horse_brewing_company-hop_hazard": {"name":"Hop Hazard","abv":5.5,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"river_horse_brewing_company","updated":"2010-07-22 20:00:20","description":"Brewing the perfect ale is truly a balancing act...hazardous work you might say. With Hop Hazard our challenge was to hand craft a malt rich base that could counterbalance a combustible five-hop blend and still leave your taste buds with enough room to enjoy a unique, crisp hop finish.","style":"American-Style Pale Ale","category":"North American Ale"}, + "russian_river_brewing-dead_leaf_green": {"name":"Dead Leaf Green","abv":5.68,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"russian_river_brewing","updated":"2010-07-22 20:00:20","description":"","style":"Special Bitter or Best Bitter","category":"British Ale"}, + "smuttynose_brewing_co-smuttynose_pumpkin_ale": {"name":"Smuttynose Pumpkin Ale","abv":5.1,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"smuttynose_brewing_co","updated":"2010-07-22 20:00:20","description":"Smuttynose Pumpkin Ale is our homage to the craft and heritage of America’s brewers. Recipes calling for the use of pumpkins in beer date back to early colonial times, when brewers sought to extend their supply of costly imported malt with locally grown ingredients, such as squash and “pompions.”\r\n\r\nIn that spirit, we brew our ale with the addition of pumpkin to the mash, along with traditional spices to create a delicious American original.","style":"Pumpkin Beer","category":"Other Style"}, + "southern_tier_brewing_co-hop_sun": {"name":"Hop Sun","abv":4.5,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"southern_tier_brewing_co","updated":"2010-07-22 20:00:20","description":"Pour Hop Sun Summer Wheat Beer into a pint glass, give it a long whiff and you’ll realize that this isn’t your average wheatbeer. Filtered to a golden clarity and dry-hopped to perfection, Hop Sun is a fantastic session ale in which flavors of wheat, barley and hops commingle to a refreshing and zesty conclusion. Hints of lemon and sweet malts waft to the fore as a touch of bitterness contributes to Hop Sun’s bright finish. Enjoy Hop Sun all summer long as a perfect balance to your outdoor recreation. Summer never tasted so good.","style":"Light American Wheat Ale or Lager","category":"Other Style"}, + "straub_brewery-straub_light": {"name":"Straub Light","abv":3.16,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"straub_brewery","updated":"2010-07-22 20:00:20","description":""}, + "tallgrass_brewing_co-oasis": {"name":"Oasis","abv":7.2,"ibu":93.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"tallgrass_brewing_co","updated":"2010-11-11 19:32:59","description":"Oasis is a Double ESB/IPAish beer that came about from playing around with one of Jeff","style":"American-Style India Pale Ale","category":"North American Ale"}, + "thirsty_dog_brewing-stud_service_stout": {"name":"Stud Service Stout","abv":3.1,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"thirsty_dog_brewing","updated":"2010-07-22 20:00:20","description":"A traditional dry Irish Stout. Very Flavorful while low in calories and alcohol. An easy drinking “session beer” that has a nice caramel flavor an smooth finish.","style":"American-Style Stout","category":"North American Ale"}, + "thirstybear_brewing-valencia_wheat": {"name":"Valencia Wheat","abv":0.0,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"thirstybear_brewing","updated":"2010-07-22 20:00:20","description":""}, + "tyskie_browary_ksiazece-tyskie_gronie": {"name":"Tyskie Gronie","abv":5.7,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"tyskie_browary_ksiazece","updated":"2010-07-22 20:00:20","description":""}, + "victory_brewing-yakima_glory": {"name":"Yakima Glory","abv":8.7,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"victory_brewing","updated":"2010-11-27 12:29:23","description":"The tenacious grip of big, juicy hop aroma and character slides smoothly into rich, dark malts. This heavyweight battle between fresh, Yakima Valley hops and dark, roasted malts is resolved harmoniously as the flavors merge to deliver complex satisfaction with a warming edge. Bask in the ","style":"American-Style India Black Ale","category":"North American Ale"}, + "wachusetts_brewing_company-green_monsta_ale": {"name":"Green Monsta Ale","abv":7.3,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"wachusetts_brewing_company","updated":"2010-07-22 20:00:20","description":"A BIG PALE ALE with an awsome balance of Belgian malts with Fuggles and East Kent Golding hops.","style":"American-Style Strong Pale Ale","category":"North American Ale"} +}