Thread: argtype_inherit() is dead code
In parse_func.c there are routines argtype_inherit() and gen_cross_product() that date from Berkeley days. The comments explain their reason for existence thus: * This function is used to handle resolution of function calls when* there is no match to the given argument types, butthere might be* matches based on considering complex types as members of their* superclass types (parent classes).** It takes an array of input type ids. For each type id in the array* that's a complex type (a class), it walksup the inheritance tree,* finding all superclasses of that type. A vector of new Oid type* arrays is returned to thecaller, listing possible alternative* interpretations of the input typeids as members of their superclasses* ratherthan the actually given argument types. The vector is* terminated by a NULL pointer.** The order of this vector isas follows: all superclasses of the* rightmost complex class are explored first. The exploration* continues from rightto left. This policy means that we favor* keeping the leftmost argument type as low in the inheritance tree* as possible. This is intentional; it is exactly what we need to* do for method dispatch.** The vector does not include thecase where no complex classes have* been promoted, since that was already tried before this routine* got called. I realized that this is effectively dead code: although it can be executed, it can never produce any useful results. The reason is that can_coerce_type() already knows that inherited rowtypes can be promoted to their parent rowtypes, and it considers that a legal implicit coercion. This means that any possible function matches based on promoting child rowtypes to ancestors were found in func_get_detail()'s first pass. If there is exactly one match then it will be taken as the correct answer and returned without calling argtype_inherit(). If there is more than one match then func_get_detail() will fail (return FUNCDETAIL_MULTIPLE), again without calling argtype_inherit(). The only way to reach argtype_inherit() is if there are *no* ancestor matches, which means that the function is a very expensive no-op that we execute just before throwing an error. I'm strongly tempted to just rip out argtype_inherit() and gen_cross_product(). Even if we suppose that we might want to resurrect the claimed functionality someday, I don't think it could be made to work this way. You'd have to put the knowledge into func_select_candidate() instead, else there'd be very weird interactions with the heuristics for resolving non-complex input argument types. Thoughts? regards, tom lane
Are you saying that the code was supposed "unflatten" the arguments of a function into a possible composite type taking into consideration the possible inheritance information of the composite type? elein On Fri, Apr 15, 2005 at 08:04:36PM -0400, Tom Lane wrote: > In parse_func.c there are routines argtype_inherit() and > gen_cross_product() that date from Berkeley days. The comments > explain their reason for existence thus: > > * This function is used to handle resolution of function calls when > * there is no match to the given argument types, but there might be > * matches based on considering complex types as members of their > * superclass types (parent classes). > * > * It takes an array of input type ids. For each type id in the array > * that's a complex type (a class), it walks up the inheritance tree, > * finding all superclasses of that type. A vector of new Oid type > * arrays is returned to the caller, listing possible alternative > * interpretations of the input typeids as members of their superclasses > * rather than the actually given argument types. The vector is > * terminated by a NULL pointer. > * > * The order of this vector is as follows: all superclasses of the > * rightmost complex class are explored first. The exploration > * continues from right to left. This policy means that we favor > * keeping the leftmost argument type as low in the inheritance tree > * as possible. This is intentional; it is exactly what we need to > * do for method dispatch. > * > * The vector does not include the case where no complex classes have > * been promoted, since that was already tried before this routine > * got called. > > I realized that this is effectively dead code: although it can be > executed, it can never produce any useful results. The reason is that > can_coerce_type() already knows that inherited rowtypes can be promoted > to their parent rowtypes, and it considers that a legal implicit > coercion. This means that any possible function matches based on > promoting child rowtypes to ancestors were found in func_get_detail()'s > first pass. If there is exactly one match then it will be taken as > the correct answer and returned without calling argtype_inherit(). > If there is more than one match then func_get_detail() will fail > (return FUNCDETAIL_MULTIPLE), again without calling argtype_inherit(). > The only way to reach argtype_inherit() is if there are *no* ancestor > matches, which means that the function is a very expensive no-op that > we execute just before throwing an error. > > I'm strongly tempted to just rip out argtype_inherit() and > gen_cross_product(). Even if we suppose that we might want to resurrect > the claimed functionality someday, I don't think it could be made to > work this way. You'd have to put the knowledge into > func_select_candidate() instead, else there'd be very weird interactions > with the heuristics for resolving non-complex input argument types. > > Thoughts? > > regards, tom lane > > ---------------------------(end of broadcast)--------------------------- > TIP 2: you can get off all lists at once with the unregister command > (send "unregister YourEmailAddressHere" to majordomo@postgresql.org) >
elein@varlena.com (elein) writes: > Are you saying that the code was supposed "unflatten" the > arguments of a function into a possible composite type taking into > consideration the possible inheritance information of the > composite type? No, it didn't do that. AFAICT the case it was supposed to handle was resolution of ambiguous function calls in what would amount to an object-oriented-programming usage style. Consider create table parent(...); create table child(...) inherits (parent); create table grandkid(...) inherits (child); create function foo(parent) returns ...; create function foo(child) returns ...; select foo(t.*) from parent t; -- uses foo(parent), of course select foo(t.*) from child t; -- uses foo(child), of course select foo(t.*) from grandkid t; -- ambiguous Our current code rejects the last with "ambiguous function call", but an OOP user would expect it to choose foo(child) because that's the nearer parent supertype. It looks to me like Postgres once did choose foo(child), and argtype_inherit() was the code that made this happen. It's been broken for a long time though --- at least since 7.0, because 7.0 rejects this example. Digging in the CVS history, it appears that I may have broken it here: http://developer.postgresql.org/cvsweb.cgi/pgsql/src/backend/parser/parse_coerce.c.diff?r1=2.35;r2=2.36;f=h It's quite possible that it failed even before that however; I don't have a pre-7.0 server handy to check it on. In any case, given the lack of complaints since 7.0, it doesn't seem anyone is trying to do this kind of thing. regards, tom lane
On Sat, Apr 16, 2005 at 03:39:55PM -0400, Tom Lane wrote: > Digging in the CVS history, it appears that I may have broken it here: > http://developer.postgresql.org/cvsweb.cgi/pgsql/src/backend/parser/parse_coerce.c.diff?r1=2.35;r2=2.36;f=h > It's quite possible that it failed even before that however; I don't > have a pre-7.0 server handy to check it on. In any case, given the lack > of complaints since 7.0, it doesn't seem anyone is trying to do this > kind of thing. Given the limitations of table inheritance, it doesn't surprise me that nobody really uses it to implement object-oriented programs. Is it really an important area to improve, or are there other priorities? I know some people wished we had better support for inheritance, but how strong is that wish? -- Alvaro Herrera (<alvherre[@]dcc.uchile.cl>) "Para tener más hay que desear menos"
Alvaro Herrera <alvherre@dcc.uchile.cl> writes: > Given the limitations of table inheritance, it doesn't surprise me that > nobody really uses it to implement object-oriented programs. True. > Is it really an important area to improve, or are there other priorities? There are certainly higher priorities, which is why I'd rather just rip out the code than try to fix it. regards, tom lane
> Is it really an important area to improve, or are there other > priorities? I know some people wished we had better support for > inheritance, but how strong is that wish? Hello, From a "people who call me" perspective. I am never asked about inheritance. Most of the people don't even know it is there. The requests I get are: Replication (obviously solved) Multi-Master Replication (working on it ;)) Table Partitioning (No great solution) IN/OUT Parameteres (being working on) Good ODBC driver (Soon to be solved) I get others of course but those are the ones that seem to raise their head the most. Sincerely, Joshua D. Drake > > -- Command Prompt, Inc., Your PostgreSQL solutions company. 503-667-4564 Custom programming, 24x7 support, managed services, and hosting Open Source Authors: plPHP, pgManage, Co-Authors: plPerlNG Reliable replication, Mammoth Replicator - http://www.commandprompt.com/
On Sun, Apr 17, 2005 at 06:56:01AM -0700, Joshua D. Drake wrote: > From a "people who call me" perspective. I am never asked about > inheritance. Most of the people don't even know it is there. > The requests I get are: Just wondering, does anybody asks you about the excessive locking (and deadlocking) on foreign keys? The business about being able to drop users and then find out they were still owners of something? I guess I worry about things too low-level that nobody really cares too much about. -- Alvaro Herrera (<alvherre[@]dcc.uchile.cl>) "Always assume the user will do much worse than the stupidest thing you can imagine." (Julien PUYDT)
As the voice of someone who has a lot of experience with some of the original inheritance, I would prefer to have the select foo(t.*) from grandkid work for completeness. However, erroring out as ambiguous is not unreasonable since we have to cast the hell out of everything usually. I would suggest putting it on the bug list to fold into the replacement arguement marshalling code. That way we won't (ideally) forget the intended behaviour and may even fix it at some point. --elein On Sat, Apr 16, 2005 at 03:39:55PM -0400, Tom Lane wrote: > elein@varlena.com (elein) writes: > > Are you saying that the code was supposed "unflatten" the > > arguments of a function into a possible composite type taking into > > consideration the possible inheritance information of the > > composite type? > > No, it didn't do that. AFAICT the case it was supposed to handle > was resolution of ambiguous function calls in what would amount to > an object-oriented-programming usage style. Consider > > create table parent(...); > > create table child(...) inherits (parent); > > create table grandkid(...) inherits (child); > > create function foo(parent) returns ...; > > create function foo(child) returns ...; > > select foo(t.*) from parent t; -- uses foo(parent), of course > > select foo(t.*) from child t; -- uses foo(child), of course > > select foo(t.*) from grandkid t; -- ambiguous > > Our current code rejects the last with "ambiguous function call", > but an OOP user would expect it to choose foo(child) because that's > the nearer parent supertype. It looks to me like Postgres once > did choose foo(child), and argtype_inherit() was the code that made > this happen. It's been broken for a long time though --- at least > since 7.0, because 7.0 rejects this example. > > Digging in the CVS history, it appears that I may have broken it here: > http://developer.postgresql.org/cvsweb.cgi/pgsql/src/backend/parser/parse_coerce.c.diff?r1=2.35;r2=2.36;f=h > It's quite possible that it failed even before that however; I don't > have a pre-7.0 server handy to check it on. In any case, given the lack > of complaints since 7.0, it doesn't seem anyone is trying to do this > kind of thing. > > regards, tom lane > > ---------------------------(end of broadcast)--------------------------- > TIP 7: don't forget to increase your free space map settings >
On Sun, Apr 17, 2005 at 06:56:01AM -0700, Joshua D. Drake wrote: > >Is it really an important area to improve, or are there other > >priorities? I know some people wished we had better support for > >inheritance, but how strong is that wish? FWIW, I think people might be more likely to use the OO features that PostgreSQL already has if there was better OO support in one or more of the languages. Oracle has some support along these lines and it was nice being able to make use of it the last time I used Oracle. I don't remember the exact details, and I don't think they're necessarily the way you'd want to do it in PostgreSQL anyway, but it was nice being able to do things like expose a type/class that knew how to pull info from the database as well as store it there. -- Jim C. Nasby, Database Consultant decibel@decibel.org Give your computer some brain candy! www.distributed.net Team #1828 Windows: "Where do you want to go today?" Linux: "Where do you want to go tomorrow?" FreeBSD: "Are you guys coming, or what?"
On Sun, 2005-04-17 at 14:04 -0400, Alvaro Herrera wrote: > On Sun, Apr 17, 2005 at 06:56:01AM -0700, Joshua D. Drake wrote: > > > From a "people who call me" perspective. I am never asked about > > inheritance. Most of the people don't even know it is there. > > The requests I get are: > > Just wondering, does anybody asks you about the excessive locking (and > deadlocking) on foreign keys? The business about being able to drop > users and then find out they were still owners of something? I guess I > worry about things too low-level that nobody really cares too much about. I know of plenty of people impacted by foreign key locking that remove specific keys in production that they have in place for testing. --
On Sunday 17 April 2005 19:30, Rod Taylor wrote: > On Sun, 2005-04-17 at 14:04 -0400, Alvaro Herrera wrote: > > On Sun, Apr 17, 2005 at 06:56:01AM -0700, Joshua D. Drake wrote: > > > From a "people who call me" perspective. I am never asked about > > > inheritance. Most of the people don't even know it is there. > > > The requests I get are: > > > > Just wondering, does anybody asks you about the excessive locking (and > > deadlocking) on foreign keys? The business about being able to drop > > users and then find out they were still owners of something? I guess I > > worry about things too low-level that nobody really cares too much about. > > I know of plenty of people impacted by foreign key locking that remove > specific keys in production that they have in place for testing. > That or put calls into try/catch mechanisms "just in case" it deadlocks even though it wouldn't with some less restrictive locking mechanism. Or come up with some type of serializing scheme to ensure deadlocks can't happen. Or several other bad schemes.... Alvaro, there are many pints waiting for you from a great many postgresql users if you can eliminate this problem with the work you're doing on shared row locks. -- Robert Treat Build A Brighter Lamp :: Linux Apache {middleware} PostgreSQL
On Sun, 2005-04-17 at 19:54 -0400, Robert Treat wrote: > On Sunday 17 April 2005 19:30, Rod Taylor wrote: > > On Sun, 2005-04-17 at 14:04 -0400, Alvaro Herrera wrote: > > > On Sun, Apr 17, 2005 at 06:56:01AM -0700, Joshua D. Drake wrote: > > > > From a "people who call me" perspective. I am never asked about > > > > inheritance. Most of the people don't even know it is there. > > > > The requests I get are: > > > > > > Just wondering, does anybody asks you about the excessive locking (and > > > deadlocking) on foreign keys? The business about being able to drop > > > users and then find out they were still owners of something? I guess I > > > worry about things too low-level that nobody really cares too much about. > > > > I know of plenty of people impacted by foreign key locking that remove > > specific keys in production that they have in place for testing. > > > > That or put calls into try/catch mechanisms "just in case" it deadlocks even > though it wouldn't with some less restrictive locking mechanism. Or come up > with some type of serializing scheme to ensure deadlocks can't happen. Or Deadlocks weren't the issue, insert serialization by the FKey locks was the issue. > several other bad schemes.... Alvaro, there are many pints waiting for you > from a great many postgresql users if you can eliminate this problem with the > work you're doing on shared row locks. Agreed. --
>>From a "people who call me" perspective. I am never asked about >>inheritance. Most of the people don't even know it is there. >>The requests I get are: > > Just wondering, does anybody asks you about the excessive locking (and > deadlocking) on foreign keys? The business about being able to drop > users and then find out they were still owners of something? I guess I > worry about things too low-level that nobody really cares too much about. I get regularly bitten by the former, and have been bitten by the latter :) Don't worry, I think your work is invaluable! They are subtle problems for people to notice, so they're not as prevalent. Chris
Can you tell me more about the good ODBC driver being worked on? I was thinking of working on this myself, but if someone is already solving the problem, that's great! -----Original Message----- From: pgsql-hackers-owner@postgresql.org [mailto:pgsql-hackers-owner@postgresql.org] On Behalf Of Joshua D. Drake Sent: Sunday, April 17, 2005 6:56 AM To: Alvaro Herrera Cc: Tom Lane; elein; pgsql-hackers@postgresql.org Subject: Re: [HACKERS] argtype_inherit() is dead code > Is it really an important area to improve, or are there other > priorities? I know some people wished we had better support for > inheritance, but how strong is that wish? Hello, From a "people who call me" perspective. I am never asked about inheritance. Most of the people don't even know it is there. The requests I get are: Replication (obviously solved) Multi-Master Replication (working on it ;)) Table Partitioning (No great solution) IN/OUT Parameteres (being working on) Good ODBC driver (Soon to be solved) I get others of course but those are the ones that seem to raise their head the most. Sincerely, Joshua D. Drake > > -- Command Prompt, Inc., Your PostgreSQL solutions company. 503-667-4564 Custom programming, 24x7 support, managed services, and hosting Open Source Authors: plPHP, pgManage, Co-Authors: plPerlNG Reliable replication, Mammoth Replicator - http://www.commandprompt.com/ ---------------------------(end of broadcast)--------------------------- TIP 7: don't forget to increase your free space map settings
The world rejoiced as decibel@decibel.org ("Jim C. Nasby") wrote: > On Sun, Apr 17, 2005 at 06:56:01AM -0700, Joshua D. Drake wrote: >> >Is it really an important area to improve, or are there other >> >priorities? I know some people wished we had better support for >> >inheritance, but how strong is that wish? > > FWIW, I think people might be more likely to use the OO features > that PostgreSQL already has if there was better OO support in one or > more of the languages. Oracle has some support along these lines and > it was nice being able to make use of it the last time I used > Oracle. I don't remember the exact details, and I don't think > they're necessarily the way you'd want to do it in PostgreSQL > anyway, but it was nice being able to do things like expose a > type/class that knew how to pull info from the database as well as > store it there. What is there, really, to add? "Object Orientation" is all about the notion of having data that is aware of its type, and where there can be a dispatching of methods against those types. There is already a perfectly functional ability to dispatch based on argument types. These essentials are there. The things beyond the essentials are extras that there is great difficulty in agreeing on. - Blind fans of the C++ language model think that OO implies certain things; - Blind fans of the Java language model think that OO implies a different certain set of things; - Ditto for Smalltalk, Python, and Perl; - Usually fans of CLOS are pretty open-minded because it is a UNION of a goodly number of object models... -- output = ("cbbrowne" "@" "cbbrowne.com") http://linuxdatabases.info/info/slony.html I'm sorry Dave, I can't let you do that. Why don't you lie down and take a stress pill?
Alvaro Herrera wrote: > On Sun, Apr 17, 2005 at 06:56:01AM -0700, Joshua D. Drake wrote: > > > From a "people who call me" perspective. I am never asked about > > inheritance. Most of the people don't even know it is there. > > The requests I get are: > > Just wondering, does anybody asks you about the excessive locking (and > deadlocking) on foreign keys? The business about being able to drop > users and then find out they were still owners of something? I guess I > worry about things too low-level that nobody really cares too much about. Those are problems you get into after using PostgreSQL heavily and are different from front-end questions about missing features. The beauty of PostgreSQL is the low number of those surprises under heavy load, but as you mentioned above we have a few of them and have to get them corrected. -- Bruce Momjian | http://candle.pha.pa.us pgman@candle.pha.pa.us | (610) 359-1001+ If your life is a hard drive, | 13 Roberts Road + Christ can be your backup. | Newtown Square, Pennsylvania19073
Alvaro Herrera wrote: > On Sun, Apr 17, 2005 at 06:56:01AM -0700, Joshua D. Drake wrote: > > >>From a "people who call me" perspective. I am never asked about >>inheritance. Most of the people don't even know it is there. >>The requests I get are: > > > Just wondering, does anybody asks you about the excessive locking (and > deadlocking) on foreign keys? I do get the deadlocking on foreign keys question sometimes but there are ways to directly help that. The business about being able to drop > users and then find out they were still owners of something? Well remember that we do a lot of the database work for our customers so we are prepared for some of this ahead of time... and now that I think about it that causes my view to be a little skewed. Sincerely, Joshua D. Drake I guess I > worry about things too low-level that nobody really cares too much about. > -- Your PostgreSQL solutions company - Command Prompt, Inc. 1.800.492.2240 PostgreSQL Replication, Consulting, Custom Programming, 24x7 support Managed Services, Shared and Dedication Hosting Co-Authors: plPHP, plPerlNG - http://www.commandprompt.com/
On Sun, Apr 17, 2005 at 07:01:41PM -0400, Christopher Browne wrote: > The world rejoiced as decibel@decibel.org ("Jim C. Nasby") wrote: > > On Sun, Apr 17, 2005 at 06:56:01AM -0700, Joshua D. Drake wrote: > >> >Is it really an important area to improve, or are there other > >> >priorities? I know some people wished we had better support for > >> >inheritance, but how strong is that wish? > > > > FWIW, I think people might be more likely to use the OO features > > that PostgreSQL already has if there was better OO support in one or > > more of the languages. Oracle has some support along these lines and > > it was nice being able to make use of it the last time I used > > Oracle. I don't remember the exact details, and I don't think > > they're necessarily the way you'd want to do it in PostgreSQL > > anyway, but it was nice being able to do things like expose a > > type/class that knew how to pull info from the database as well as > > store it there. > > What is there, really, to add? > > "Object Orientation" is all about the notion of having data that is > aware of its type, and where there can be a dispatching of methods > against those types. > > There is already a perfectly functional ability to dispatch based on > argument types. > > These essentials are there. Yes, but they're only there when it comes to storing data. There's nothing allowing you to cohesively combine code and data. An object should be able to have methods attached to it, for example. And that functionality is essentially missing. There's no way to present a combined set data and code that operates on that data. It doesn't really matter why this kind of functionality is missing; the fact that it is missing means it's much less likely that any of the OO stuff will be used. I think the current limitations (foreign keys, and cross-table constraints) are issues as well. It might also help if the docs had some info about how inherited tables worked 'under the covers', so people knew what kind of overhead they implied. -- Jim C. Nasby, Database Consultant decibel@decibel.org Give your computer some brain candy! www.distributed.net Team #1828 Windows: "Where do you want to go today?" Linux: "Where do you want to go tomorrow?" FreeBSD: "Are you guys coming, or what?"
> -----Original Message----- > From: Jim C. Nasby [mailto:decibel@decibel.org] > Sent: Tuesday, April 19, 2005 5:56 PM > To: Christopher Browne > Cc: pgsql-hackers@postgresql.org > Subject: Re: [HACKERS] argtype_inherit() is dead code > > [...] > On Sun, Apr 17, 2005 at 07:01:41PM -0400, Christopher Browne wrote: > > [...] > > "Object Orientation" is all about the notion of having data that > > is aware of its type, and where there can be a dispatching of > > methods against those types. > > > > There is already a perfectly functional ability to dispatch based > > on argument types. > > > > These essentials are there. Well, if you go with Bjarne Stroustrup's formulation of OOP (which is, of course, by no means exclusively authoritative), the core of OOP is encapsulation, inheritance, and polymorphism. Inherited tables provide the second, overloaded functions provide the third, but the security model is left providing the first. However, I would say that the first property is the most essential for OOP, because in my view, OOP is about *data hiding*. In particular, it's about separating the implementation from the interface, and forcing users to access objects through the interface. While such a design philosophy is *possible* with Postgres, it is by no means encouraged or *easy*. Furthermore, it probably doesn't make sense in all contexts. One way to think about an object-relational database is as a set of persistent objects stored in well-known containers. While traditional programming languages offer several common access methods for containers, the point of a query language is to offer an extremely powerful and generalized container access system. However, this access system is really an implementation detail of the object system, while at the same time being the primary means of object interaction. In terms of manipulating data, it's really about as OOP as passing around raw pointers to everything. From this perspective, DBs will not support OOP while SQL remains the primary access method; and there is no reason to believe that people will give up SQL in favor of a more OOP-like interface. > Yes, but they're only there when it comes to storing data. There's > nothing allowing you to cohesively combine code and data. I agree entirely. And I also agree that in many cases, there is no sensible way to do so. One of the ways in which DBs are different from programming language objects is in the data decomposition. Most PLs have self-contained objects whose data is primarily localized within one structure that is more or less contiguous in memory. DBs, on the other hand, tend to have objects that may span multiple tables, because this is the most efficient way of storing the data. In a way, the relational model is the antithesis of the OOP model. The central theme of the relational model is *data-sharing*. The idea that data should be decomposed and the common pieces factored out. Whereas, OOP says that it is the *functionality* that should be factored out into a minimal interface. > An object should be able to have methods attached to it, for example. I don't think that's sufficient. To support encapsulation, you also need to enforce access to the data through the method interface. Else, you can simulate methods with stored procedures. > And that functionality is essentially missing. There's no way to > present a combined set data and code that operates on that data. That's encapsulation. And it's missing. But for a good reason. > It doesn't really matter why this kind of functionality is missing; > the fact that it is missing means it's much less likely that any of > the OO stuff will be used. Actually, it *does* matter why it's missing. The reason it's missing tells us why people don't use the OOP features of the DB. What needs to be done is to construct a consistent theory of how the relational model and the OOP model can be integrated. The OOP model is about data integrity, maintaining object invariants, ensuring program correctness, etc. The relational model is about performance, storing data efficiently, querying it efficiently, etc. These are competing goals, and it may well be that a good object-relational theory simply develops a framework in which the tradeoffs are explicitly stated and describes how to implement different points in the design space in a consistent way. I realize that there is some existing work with object-relational modelling, but my impression is that such work is still fairly immature and scattered. > I think the current limitations (foreign keys, and cross-table > constraints) are issues as well. It might also help if the > docs had some info about how inherited tables worked 'under the > covers', so people knew what kind of overhead they implied. I don't think inherited tables work in an entirely intuitive way. It certainly doesn't help that viewing an inherited table through pgAdmin shows records that aren't returned by an equivalent query. I think the problem is that OOP concepts have been "bolted on" to the relational model without really thinking hard about what functionality should be available to really support ORM. __ David B. Held Software Engineer/Array Services Group 200 14th Ave. East, Sartell, MN 56377 320.534.3637 320.253.7800 800.752.8129